Entity reference · v1
products
PROD-NNNNNNtable productssoft deleteTop-level product surfaces. Can be nested via parent_id.
Code on products and modules
A product or module can be attached to the code that builds it. Attach a GitHub organization to cover every repo under it, including repos the GitHub App sees later, or attach single repos. A repo belongs to what is attached to it and to what is attached to its organization.
Only organizations and repos already connected through the GitHub App (Settings, Integrations) can be attached.
Naming code
| What | Accepted forms |
|---|---|
| Organization | Its login (ProductOS-Inc), a github.com link to it, or its connection UUID |
| Repo | owner/name (ProductOS-Inc/ProductOS), a github.com link to it or anything in it, or its UUID |
Matching ignores case. A name that matches nothing connected is refused with a 400 that lists the organizations and repos that are.
Routes
The product or module in the path is a UUID or a sequence id (PROD-000004, MOD-000045).
| Method | Path | Body | Does |
|---|---|---|---|
GET | /api/v1/products/{id}/code | What is attached: orgs and repos. ?include=options adds available, every connected organization and repo. | |
POST | /api/v1/products/{id}/code | { "orgs": [...], "repos": [...] } | Attaches them. A link already there is listed under detail.existing and not written again. |
DELETE | /api/v1/products/{id}/code | { "orgs": [...], "repos": [...] } | Detaches them. A link that is not there is skipped. |
The same three routes exist under /api/v1/modules/{id}/code. Writes need member and edit in the Products area, take up to 50 names per call, and return what is attached afterwards with an inverse that undoes the call.
curl -X POST https://productos.ca/api/v1/products/PROD-000004/code \
-H "Authorization: Bearer $PRODUCTOS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "orgs": ["ProductOS-Inc"] }'GET /api/v1/integrations/github-app/repos lists every connected repo with scopes: the products and modules it belongs to, each with via set to repo or org.
Over MCP the same writes are attach_code and detach_code. See MCP server.
People and roles
A team member can hold a role on a product or a module: Owner, Product Manager, Engineering Lead, Design Lead, Stakeholder, Contributor, or any other role up to 60 characters. Each person has one role per product or module. The app shows them in the People section of the product and module dialogs (behind the product-roles flag) and on the person's team member dialog.
| Method | Path | Body | Does |
|---|---|---|---|
GET | /api/v1/products/{id}/links/members | The people on the product: team_member_id, role and the team member row. | |
POST | /api/v1/products/{id}/links/members | { "ids": [...], "extra": { "role": "Product Manager" } } | Gives them that role. Linking a person again updates the role in place. Without role they get Contributor. |
DELETE | /api/v1/products/{id}/links/members/{teamMemberId} | Takes them off the product. |
The same routes exist under /api/v1/modules/{id}/links/members, and from the person's side under /api/v1/team-members/{id}/links/products and /links/modules. A blank role, null, or one over 60 characters is a 422. Writes need member or higher.
curl -X POST https://productos.ca/api/v1/products/PROD-000004/links/members \
-H "Authorization: Bearer $PRODUCTOS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "ids": ["TEAM-000003"], "extra": { "role": "Owner" } }'Endpoints
/api/v1/products/api/v1/products/api/v1/products/{id}/api/v1/products/{id}/api/v1/products/{id}/api/v1/products/{id}/restore/api/v1/products/{id}/links/personas/api/v1/products/{id}/links/personas/api/v1/products/{id}/links/personas/{targetId}/api/v1/products/{id}/links/members/api/v1/products/{id}/links/members/api/v1/products/{id}/links/members/{targetId}Writable fields
* required on create. PATCH treats every field as optional.
| Field | Type | Constraints | Notes |
|---|---|---|---|
| name* | string | ||
| description | text | ||
| icon | string | ||
| status | string | active | beta | deprecated | sunset | planned | |
| owner | string | ||
| parent_id | uuid | ||
| sort_order | integer | ||
| is_draft | boolean | ||
| metadata | json |
Listing & filtering
- Pagination:
?limit=50&offset=0· max limit 200 - Default sort:
?sort=sort_order&order=asc - Sortable fields: created_at · updated_at · name · sort_order · id_number
- Search (?q=): name, description
- Filters: ?status= ?parent_id= ?is_draft= , repeat any filter to OR multiple values
- Trash: hidden by default; pass
?include_deleted=true - Expand:
?expand=personas,members
Examples
curl 'https://productos.ca/api/v1/products?limit=10' \
-H "Authorization: Bearer $TOKEN"Try it out
Paste a token and hit a real endpoint. The request goes from your browser straight to /api/v1/products. The token is held in this page only for the request and is never stored.
Issue tokens in Settings → API tokens. Token never leaves this page.
GET/api/v1/products?limit=3