ProductOSDocs

Entity reference · v1

products

PROD-NNNNNNtable productssoft delete

Top-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

WhatAccepted forms
OrganizationIts login (ProductOS-Inc), a github.com link to it, or its connection UUID
Repoowner/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).

MethodPathBodyDoes
GET/api/v1/products/{id}/codeWhat 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.

MethodPathBodyDoes
GET/api/v1/products/{id}/links/membersThe 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

GET/api/v1/products
POST/api/v1/products
GET/api/v1/products/{id}
PATCH/api/v1/products/{id}
DELETE/api/v1/products/{id}
POST/api/v1/products/{id}/restore
GET/api/v1/products/{id}/links/personas
POST/api/v1/products/{id}/links/personas
DELETE/api/v1/products/{id}/links/personas/{targetId}
GET/api/v1/products/{id}/links/members
POST/api/v1/products/{id}/links/members
DELETE/api/v1/products/{id}/links/members/{targetId}

Writable fields

* required on create. PATCH treats every field as optional.

FieldTypeConstraintsNotes
name*string
descriptiontext
iconstring
statusstringactive | beta | deprecated | sunset | planned
ownerstring
parent_iduuid
sort_orderinteger
is_draftboolean
metadatajson

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

curlbash
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.

Try it out

Issue tokens in Settings → API tokens. Token never leaves this page.

GET/api/v1/products?limit=3

Generated from the API's entity configuration. This page is rebuilt on every deploy, so it always matches the live API.