---
title: "Upsert My Company"
method: PUT
path: "/api/company"
tags: ["companies"]
---

# Upsert My Company

`PUT /api/company`

## Request

- Base URL: `https://api.parlel.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.parlel.com/api/company`
- Auth: none declared

Create or replace the signed-in account's company page.

200 on both create and update rather than 201 on create, matching
``PUT /api/profile``: the client treats this as one "save" action, and
branching its success handler on a status code gains it nothing that
``exists`` in the body does not already say.

Idempotent in the sense that matters: the editable row is looked up by
``user_id`` with ``origin`` null/claimed, so saving twice updates that one
page and never overwrites an ``imported`` directory listing the same account
may also own.

## The handle check happens twice, on purpose

Once here against BOTH tables (``handle_owner``) and once in the database
against this table's unique index. The first is the only thing that can catch
a collision with a PERSON -- no index spans the two tables, see
``api.handles`` -- and it produces a clear 409 before anything is written. The
second closes the race the first cannot: between the check and the INSERT,
another company can take the name, and only the index is authoritative about
that.

## ``published_at`` is set once and never cleared

Stamped the first time ``is_public`` goes true, left alone on every later
save, and deliberately NOT cleared when a page is unpublished. It answers "how
long have they been here", which is a trust signal that survives a company
taking its page down for a fortnight; clearing it would make a returning
company look brand new, and re-publishing would then quietly mint a fresh
founding date for an account that has been here two years.

## Headers

- `authorization` string, nullable

## Request body

- CompanyUpsert: The whole company page. ``PUT`` semantics: absent means cleared. A PUT and not a PATCH for the reason ``profiles.ProfileUpsert`` gives: this is a form, the editor holds every field on screen and submits all of them, so "the body is the new state" is both the honest description and the only one that lets somebody delete their tagline without a null-versus-absent convention every client then has to get right. The sharp edge, stated plainly for whoever writes that client: **omitting ``is_public`` unpublishes.** A partial body does not preserve the old visibility. ``name`` is the one required field besides the handle. A company with no name is a page that cannot render a heading, and unlike a person -- whose page can honestly fall back to their handle -- an organisation choosing to be listed has by definition decided what it is called.
  - `handle` string, required
  - `name` string, required
  - `tagline` string, nullable
  - `about` string, nullable
  - `location` string, nullable
  - `size` '1-10' | '11-50' | '51-200' | '201-500' | '501-1000' | '1001-5000' | '5000+', nullable
  - `industry` string, nullable
  - `founded_year` integer, nullable
  - `website_url` string, nullable: must be a http:// or https:// URL with a hostname and no credentials
  - `linkedin_url` string, nullable: must be a http:// or https:// URL with a hostname and no credentials
  - `x_url` string, nullable: must be a http:// or https:// URL with a hostname and no credentials
  - `github_url` string, nullable: must be a http:// or https:// URL with a hostname and no credentials
  - `careers_url` string, nullable: must be a http:// or https:// URL with a hostname and no credentials
  - `contact_email` string, nullable
  - `is_public` boolean

## Response `200`

Successful Response

- object

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/parlel/apis/parlel-api.md) · [All operations](https://skmtc.dev/parlel/apis/parlel-api/llms.txt) · [OpenAPI document](https://skmtc.dev/parlel/apis/parlel-api/revisions/4d41a8f5cfc9?raw)
