/api/v1/companiesCreate a company
The approved contract does not provide a longer purpose statement for this operation.
- App
- People & Companies
- Contract
- People & Companies API 1.5.0
- Lifecycle
- preview
- Runtime
- restricted
- Operation ID
- postCompanies
- Canonical origin
- https://directory.runonvision.com
Availability: This is a preview contract with runtime status restricted. Publication documents an approved interface; it does not imply public production access.
Purpose and use cases
Create a company
The owning App is Vision People & Companies. Directory owns these facts, its workspace authorization, and its change feed. Apps store a record's id and keep their own data; app-specific fields go in namespaced extensions.
Business use cases and out-of-scope behavior are not yet declared in the approved OpenAPI description.
Request
POST https://directory.runonvision.com/api/v1/companies
Authentication and authorization
Send a Vision-issued bearer token for the exact App audience. The token must include the directory entitlement and satisfy the directory workspace required binding.
- Required scope:
directory:companies:write
Human roles are not declared in OpenAPI. Record-level and business permission checks remain the owning App's authority.
Headers
Authorization: Bearer <access-token>— required.Accept: application/json— recommended where a JSON response is declared.Content-Type: application/json— required for the documented request representation.
Undeclared tracing, idempotency, conditional-request, and version headers are not assumed on this page.
Path and query parameters
| Name | Location | Type | Rules | Description |
|---|---|---|---|---|
| Idempotency-Key | header | string | optional; pattern ^[A-Za-z0-9._:-]{16,160}$ | Makes a write safe to retry: the same key replays the first answer. |
| Vision-Acting-For | header | string | optional; maximum length 600 | An App only: the percent-encoded name of the person it is acting for, stored with the change. A name, never a contact value. |
Request body
Media type: application/json. The body is required.
| Field | Type | Rules | Description |
|---|---|---|---|
| name | string | required; minimum length 1; maximum length 200 | Not described in the contract. |
| legalName | string | null | optional | Not described in the contract. |
| website | string | null | optional | Not described in the contract. |
| notes | string | null | optional | Not described in the contract. |
| preferredChannel | email | phone | sms | post | null | optional | Not described in the contract. |
| preferredLanguage | string | null | optional | Not described in the contract. |
| doNotContact | boolean | optional | Not described in the contract. |
| emails | array | optional; default []; maximum items 20 | Not described in the contract. |
| phones | array | optional; default []; maximum items 20 | Not described in the contract. |
| socialLinks | array | optional; default []; maximum items 20 | Not described in the contract. |
| addresses | array | optional; default []; maximum items 10 | Not described in the contract. |
Runnable examples
Placeholder values are generated from the approved schema and are not live credentials or customer data. Replace every angle-bracket or shell variable value. Examples cannot be run until the App has a routed base URL and your client has the required grant.
curl 'https://directory.runonvision.com/api/v1/companies' \
--request POST \
--header 'authorization: Bearer ${ACCESS_TOKEN}' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '{
"name": "<name>",
"legalName": "<legalName>",
"website": "<website>",
"notes": "<notes>",
"preferredChannel": "email",
"preferredLanguage": "<preferredLanguage>",
"doNotContact": true,
"emails": [],
"phones": [],
"socialLinks": [],
"addresses": []
}'const response = await fetch('https://directory.runonvision.com/api/v1/companies', {
method: 'POST',
headers: {
authorization: `Bearer ${ACCESS_TOKEN}`,
accept: 'application/json',
'content-type': 'application/json',
},
body: JSON.stringify({
"name": "<name>",
"legalName": "<legalName>",
"website": "<website>",
"notes": "<notes>",
"preferredChannel": "email",
"preferredLanguage": "<preferredLanguage>",
"doNotContact": true,
"emails": [],
"phones": [],
"socialLinks": [],
"addresses": []
}),
}
if (!response.ok) throw new Error(`Vision API ${response.status}`)
const result = await response.json()import json
import urllib.parse
import urllib.request
request = urllib.request.Request(
'https://directory.runonvision.com/api/v1/companies',
data=json.dumps({"name":"<name>","legalName":"<legalName>","website":"<website>","notes":"<notes>","preferredChannel":"email","preferredLanguage":"<preferredLanguage>","doNotContact":true,"emails":[],"phones":[],"socialLinks":[],"addresses":[]}).encode(),
headers={
"Accept": "application/json",
"Authorization": "Bearer <ACCESS_TOKEN>",
"Content-Type": "application/json"
},
method='POST',
)
with urllib.request.urlopen(request, timeout=30) as response:
result = json.load(response)The current TypeScript reference client covers Platform token exchange and core reads. No native SDK helper is declared for this operation; use the HTTP contract directly and follow the SDK guidance.
Responses
| Status | Meaning | Schema |
|---|---|---|
| 201 | OK | object |
| 400 | Some details are missing or not valid | object |
| 401 | No credential, or not a valid one | object |
| 403 | Not allowed (also returned for an unknown workspace) | object |
| 404 | No such record in this workspace | object |
| 409 | Duplicate on the record, stale version, reused idempotency key, or sample_record_outbound_blocked (a sample record cannot be given permission to be contacted) | object |
| 413 | The request body is over 64 KiB (code payload_too_large) | object |
| 415 | The request body is not application/json (code unsupported_media_type) | object |
| 500 | An unexpected defect (code internal_error); not retryable, report the requestId | object |
| 503 | The database is temporarily unavailable (code dependency_unavailable); retry with backoff | object |
Success response schema
| Field | Type | Rules | Description |
|---|---|---|---|
| record | object | required | Not described in the contract. |
| record.schemaVersion | "vision-directory-company.v1" | optional | Not described in the contract. |
| record.name | string | optional | Not described in the contract. |
| record.legalName | string | null | optional | Not described in the contract. |
| record.website | string | null | optional | Not described in the contract. |
| record.id | string · uuid | optional | The id other apps store to reference this record. |
| record.isSample | boolean | optional | True for sample data Core added to a new business; it goes when the owner removes sample data. |
| record.notes | string | null | optional | Not described in the contract. |
| record.preferredChannel | email | phone | sms | post | null | optional | Not described in the contract. |
| record.preferredLanguage | string | null | optional | Not described in the contract. |
| record.doNotContact | boolean | optional | Not described in the contract. |
| record.primaryEmail | string | null | optional | Not described in the contract. |
| record.primaryPhone | string | null | optional | Not described in the contract. |
| record.emails | array | optional; maximum items 50 | Not described in the contract. |
| record.phones | array | optional; maximum items 50 | Not described in the contract. |
| record.socialLinks | array | optional; maximum items 50 | Not described in the contract. |
| record.addresses | array | optional; maximum items 50 | Not described in the contract. |
| record.affiliations | array | optional | Current roles first; at most 100 (see affiliationsTotal). |
| record.affiliationsTotal | integer | optional | All roles, including any not listed. |
| record.includesSample | boolean | optional | True when affiliationsTotal counted a role that is sample data. |
| record.consents | array | optional | Not described in the contract. |
| record.extensions | object | optional | Keyed by app id, then field key. |
| record.recordVersion | integer | optional; minimum 1 | Not described in the contract. |
| record.createdAt | string · date-time | optional | Not described in the contract. |
| record.updatedAt | string · date-time | optional | Not described in the contract. |
| record.deletedAt | string | null · date-time | optional | Not described in the contract. |
| duplicateHints | array | required | Not described in the contract. |
{
"record": {
"schemaVersion": "vision-directory-company.v1",
"name": "<name>",
"legalName": "<legalName>",
"website": "<website>",
"id": "00000000-0000-4000-8000-000000000001",
"isSample": true,
"notes": "<notes>",
"preferredChannel": "email",
"preferredLanguage": "<preferredLanguage>",
"doNotContact": true,
"primaryEmail": "<primaryEmail>",
"primaryPhone": "<primaryPhone>",
"emails": [
{
"id": "00000000-0000-4000-8000-000000000001",
"label": "<label>",
"value": "<value>",
"rawValue": "<rawValue>",
"isPrimary": true
}
],
"phones": [
{
"id": "00000000-0000-4000-8000-000000000001",
"label": "<label>",
"value": "<value>",
"e164": "<e164>",
"rawValue": "<rawValue>",
"isPrimary": true
}
],
"socialLinks": [
{
"id": "00000000-0000-4000-8000-000000000001",
"network": "<network>",
"url": "<url>"
}
],
"addresses": [
{
"id": "00000000-0000-4000-8000-000000000001",
"label": "<label>",
"line1": "<line1>",
"line2": "<line2>",
"city": "<city>",
"region": "<region>",
"postalCode": "<postalCode>",
"countryCode": "<countryCode>",
"country": "<country>",
"isPrimary": true,
"formatted": "<formatted>"
}
],
"affiliations": [
{
"id": "00000000-0000-4000-8000-000000000001",
"personId": "00000000-0000-4000-8000-000000000001",
"personName": "<personName>",
"companyId": "00000000-0000-4000-8000-000000000001",
"companyName": "<companyName>",
"role": "<role>",
"startedOn": "2026-08-21",
"endedOn": "2026-08-21",
"current": true,
"isSample": true
}
],
"affiliationsTotal": 1,
"includesSample": true,
"consents": [
{
"channel": "<channel>",
"purpose": "<purpose>",
"status": "granted",
"source": "<source>",
"recordedAt": "2026-08-21T18:00:00Z"
}
],
"extensions": {},
"recordVersion": 1,
"createdAt": "2026-08-21T18:00:00Z",
"updatedAt": "2026-08-21T18:00:00Z",
"deletedAt": "2026-08-21T18:00:00Z"
},
"duplicateHints": [
{
"entityType": "person",
"id": "<id>",
"name": "<name>",
"matchedOn": "email",
"value": "<value>",
"isSample": true
}
]
}Errors and troubleshooting
Error bodies and codes are shown only where the approved contract declares them. Use status, the declared error schema, and any correlation identifier returned by the App; do not infer that two Apps share one envelope.
400— Some details are missing or not valid401— No credential, or not a valid one403— Not allowed (also returned for an unknown workspace)404— No such record in this workspace409— Duplicate on the record, stale version, reused idempotency key, or sample_record_outbound_blocked (a sample record cannot be given permission to be contacted)413— The request body is over 64 KiB (code payload_too_large)415— The request body is not application/json (code unsupported_media_type)500— An unexpected defect (code internal_error); not retryable, report the requestId503— The database is temporarily unavailable (code dependency_unavailable); retry with backoff
Collection behavior
Pagination: not declared. Filtering/search: not declared. Sorting: not declared.
Cursor lifetime, cursor binding, stable ordering, maximum traversal, and unknown-filter behavior are not assumed unless stated by a parameter description or schema constraint above.
Operational behavior
- Rate limit: No operation-specific limit or 429 response is declared.
- Retry: Retry only when the documented error model or response headers authorize it. Timeout and backoff values are not declared by this operation.
- Idempotency: No idempotency header or replay window is declared.
- Concurrency: No ETag, If-Match, or optimistic-version header is declared.
Security and privacy
- Keep client credentials and bearer tokens on trusted servers.
- Send only to the exact approved HTTPS origin and audience.
- Treat response data according to the owning App's classification and retention policy; the OpenAPI contract does not itself grant data access.
- Do not log credentials, tokens, complete private payloads, or secrets.
Edge cases and known documentation gaps
- No detailed operation description.
- The request body has no purpose or conditional-rule description.
- Business roles, timeout, retry budget, rate value, idempotency window, and concurrency behavior are absent unless explicitly stated above.
Related operations and workflows
- GET /api/v1/companies — List or search companies
- POST /api/v1/companies/fetch — Get up to 100 companies by id, in one call; ids with no record come back in missing
- GET /api/v1/companies/{id} — Get a company by id
- PATCH /api/v1/companies/{id} — Change a company (absent fields are left alone)
- DELETE /api/v1/companies/{id} — Delete a company (soft: restorable)
- POST /api/v1/companies/{id}/restore — Restore a deleted company
- POST /api/v1/companies/{id}/consents — Record a contact permission (appends to the history)
- POST /api/v1/companies/{id}/emails — Add emails
Contract history and evidence
- Contract version:
1.5.0 - Approved snapshot SHA-256:
e9a21bef4722cf744b269884fe18e2c42858995fb6c78f16ae0aef076c00f5f7 - Approval: Anthony, 2026-11-03T01:23:29.788Z (anthony-docs-live-20261102)
- Download the authoritative OpenAPI 3.1 snapshot