Visena Documentation
Partner API

Reference

API reference

The endpoint-by-endpoint contract for the Partner API: every route, parameter, request body, response shape and status code, with a copy-ready example on each endpoint. This page carries what all of them share.

The endpoint reference is kept in English in both language trees. It mirrors the API contract verbatim and there is no generator to rebuild it from, so a translated copy would drift from the API the first time an endpoint changes.

The base URL

Examples use the placeholder base URL https://api.visena.example and the instance name acme; replace both with the values you received during onboarding. Every business path starts with the instance name, then the API version, then a locale, then the resource:

https://api.visena.example/acme/api/v1/en/person/xK9mQ2
/acmeinstance name /enlocale (en · no) /personresource

Resource names are singular, and the path carries no partner/ segment: it is /{instanceName}/api/v1/{locale}/person, never /partner/api/v1/en/persons. Every endpoint in the reference is written out with those two leading path parameters in place.

The token endpoint is the one exception, because it is not a business endpoint: it lives at /{instanceName}/auth/api/v1/token — the instance name, then the fixed auth surface, and no locale segment.

Resources

Nine resources make up the surface today. Each has its own page with an endpoint list, the full contract of every endpoint, and the object shapes those endpoints exchange.

Reading this reference

Every resource page is laid out the same way, so the second one you read needs no orientation:

  • An endpoint list at the top — method, path and summary, each linking down to the endpoint.
  • One section per endpoint: the method and full path, what it does, its parameters, its request body, a table of the responses it can give, and a curl example.
  • Object shapes at the foot: every request and response body the page refers to, field by field.

Endpoint anchors are stable. The id of a section is the operation id from the contract — #createPerson, #listPersonsByCursor — and it is the same in both language trees, so a deep link you paste into a ticket keeps working.

What the endpoint pages deliberately leave out: the two leading path parameters, which are on this page; and the four shared errors, which are on Errors and troubleshooting. For worked examples, integration patterns and the guided introduction, read the guide first — Getting started is the short path from a credential to a first response.

The same surface is available as an OpenAPI 3.0 document for import into Postman, Insomnia or a code generator — ask your Visena contact for partner-openapi.json.

Authentication

One standard OAuth 2.0 request turns your clientId and clientSecret into a short-lived bearer token, scoped to exactly what you ask for. Every other endpoint in this reference requires that token. How to create the credential in the interface, and a token cache worth copying, are on Credentials and tokens.

Scopes

The scope form field is mandatory — a token request without it is rejected with 400 invalid_scope. You may ask for your credential's full scope set or any subset of it:

Scope Allows
read All safe methods — GET reads, lists, downloads and change feeds.
write All mutations — POST, PUT, PATCH, DELETE.
act-as-user:read Safe methods with an on-behalf credential (one issued to act for a specific named user).
act-as-user:write Mutations with an on-behalf credential. Mutation responses carrying an entity body echo the acting user's masked id in actedBy.

The two families never mix: a plain credential is registered in the read/write family, an on-behalf credential in the act-as-user:* one — and within its family a credential created as Bare lesing («read only») holds the read scope alone. Asking for a scope your credential does not hold returns 400 invalid_scope. Request only what the job needs — an export job has no business holding write.

Token lifecycle

  • Tokens are opaque. Never decode or inspect an access_token; treat it as random text.
  • expires_in is authoritative. Read the lifetime from every response; never hardcode it.
  • There is no refresh token. When a token expires, run the same request again — the exchange is cheap.
  • Cache and reuse. Mint once, reuse until roughly 60 seconds before expiry, then mint again. Never mint per request.

Using the token

Send it on every request as Authorization: Bearer <access_token>. The token only works on the instance it was minted for, and scopes are enforced per request: safe methods need the read scope, mutations the write scope.

Exchange a credential for an access token

POST /{instanceName}/auth/api/v1/token

Standard OAuth 2.0 client_credentials grant. Authenticate with HTTP Basic (clientId as user name, clientSecret as password) or with client_id/client_secret form fields. The scope parameter is mandatory and must be a subset of the scopes the credential holds — the platform never picks a default.

There is no refresh token: when the token expires, run the same request again. Cache the token and reuse it until shortly before expires_in runs out.

Request body

application/x-www-form-urlencodedobject

Responses

Status Description
200 The access token.
TokenResponse · application/json
400 invalid_scope — scope missing or not held by this credential; invalid_request/invalid_grant — malformed body or wrong grant type.
401 invalid_client — wrong client_id/client_secret, or the credential was revoked.

This endpoint does not answer with a problem document. It fails the OAuth 2.0 way, with an error code and an error_description — see Errors and troubleshooting.

Example

Request — curl
curl -X POST https://api.visena.example/acme/auth/api/v1/token \
  -u "acme-partner-3f9a:$CLIENT_SECRET" \
  -d "grant_type=client_credentials" \
  -d "scope=read write"
Response — 200 OK
{
  "access_token": "PhVYGf3qK9t2Zx0CmW8rNbL5aTuJdEwSyAoQiHkXgMfDzUcRvBnOl46E…",
  "scope": "read write",
  "token_type": "Bearer",
  "expires_in": 86399
}

Object shapes

The bodies the token exchange returns. Every other object shape is documented on the resource page that uses it.

TokenResponse

Field Type Description
access_token required string The bearer token. Opaque — send it back verbatim.
token_type required string
expires_in required integer Seconds until the token expires. Read it from every response; never hardcode a lifetime.
scope required string The scopes actually granted, space-separated.

OAuthError

Field Type Description
error required string Machine-readable OAuth 2.0 error code, e.g. invalid_client, invalid_scope.
error_description string Human-readable explanation.

URL shape

Everything from here down is shared by every endpoint on every resource page. The resource pages assume it and never repeat it.

Business endpoints live at /{instanceName}/api/v1/{locale}/…, and those two path parameters are the reason no endpoint table lists them. {instanceName} is the customer instance your credential belongs to — a token minted for one instance gets 403 everywhere else, and an instance name that does not exist gets 404 on every endpoint at once. {locale} (en or no) selects the language of human-readable text, most visibly the detail of an error; it never changes field names, formats or behaviour.

Headers

Header Value
Authorization Bearer <access_token> — on every request.
Content-Type application/json on POST and PUT · application/merge-patch+json on PATCH · multipart/form-data on file uploads · omit on GET and DELETE.

Ids, timestamps, fields

  • Masked ids. Every id is an opaque string like xK9mQ2. Store and echo it verbatim; never parse, order or construct one. A malformed id answers 400; a well-formed id that does not resolve answers 404 as a path id, or 422 as a reference inside a body.
  • Timestamps. created and modified are ISO-8601 date-times with an offset. Date-only fields, for example dueDate, are plain YYYY-MM-DD.
  • Absent means absent. Optional fields with no value are omitted from response JSON, not sent as null.
  • Audit overrides. Create bodies accept optional created/createdBy, and update bodies modified/modifiedBy, to attribute a change when migrating data; the defaults are server-now and the credential's owner. An unresolvable person reference answers 422.
  • actedBy. Mutations made with an on-behalf token echo the acting user's masked id in the response body.

Updates: PUT vs PATCH

PUT replaces the whole record — anything you leave out is treated as empty. PATCH is RFC 7396 JSON Merge Patch and requires Content-Type: application/merge-patch+json; anything else answers 415. A field you include is updated, a field you set to null is cleared, a field you omit is untouched, and an empty body {} is a valid no-op.

Pagination — three read modes

Every collection offers up to three list variants. The contract is identical across resources, so the shape below is the shape you get on /company and /activity too; each resource page adds only its own filters and projections. Which mode fits which job — and how to build a sync that does not lose rows — is on Lists, paging and sync.

Mode Path Paging parameters Response envelope
Offset GET /person offset (default 0) · limit Items, a real total, and navigation links.
Cursor GET /person/cursor cursor · limit Items, nextCursor/prevCursor, and links. No total — keyset paging skips the count.
Timeline GET /person/timeline since · key (created or modified) · cursor · limit The cursor envelope, over everything created or modified since the lower bound.

Omit cursor for the first page; a cursor minted for one key is rejected on another with 400. On person and company, all three variants also take view, which selects the item projection: lean (the default) returns the compact list item, full returns for every row the same body the single-record read returns, and view=full caps limit at 100.

Offset envelope
{
  "totalItems": 143,
  "totalPages": 8,
  "page": 0,
  "size": 20,
  "items": [ { "id": "xK9mQ2", … }, … ],
  "links": {
    "self":  "/acme/api/v1/en/person?offset=0&limit=20",
    "first": "/acme/api/v1/en/person?offset=0&limit=20",
    "next":  "/acme/api/v1/en/person?offset=20&limit=20",
    "last":  "/acme/api/v1/en/person?offset=140&limit=20"
  }
}
Cursor envelope
{
  "items": [ { "id": "xK9mQ2", … }, … ],
  "nextCursor": "Qm5PbDQ2RXhLOW1RMnRaeDBDbVc4…",
  "prevCursor": "TmJMNWFUdUpkRXdTeUFvUWlIa1hn…",
  "links": {
    "self": "/acme/api/v1/en/person/cursor?limit=20",
    "next": "/acme/api/v1/en/person/cursor?cursor=Qm5PbDQ2RXhLOW1RMnRaeDBDbVc4…&limit=20"
  }
}
Member What it is
items The rows on this page, in the projection the resource's view parameter selects where it offers one. Cursor and timeline pages are in ascending key order.
totalItems totalPages page size Offset mode only: the total number of matching rows, the number of pages at the current page size, the zero-based index of this page, and the page size actually applied.
nextCursor prevCursor Cursor and timeline modes only. Opaque page cursors; nextCursor is absent on the last page and prevCursor on the first.
links RFC 8288 navigation links in the body: self, next and prev where they apply, plus first and last on the offset endpoint.

Follow the links, do not build URLs. Each link preserves your filters and is a valid path against the same base URL. A missing next means the last page, and cursors are opaque — never edit one. Documents add a placement-level feed of their own on top of these three: Document changes.

Errors

Any non-2xx from the business API is an RFC 9457 problem document with Content-Type: application/problem+json. Branch on status and type; show detail to people — it is localized by the {locale} segment and names the offending field on a validation failure — but never parse it.

Problem document
{
  "timestamp": "2026-08-21T11:58:21.123Z",
  "status": 422,
  "type": "urn:visena:validation:illegal-argument",
  "errorType": "ILLEGAL_ARGUMENT",
  "title": "Unprocessable Entity",
  "detail": "companyId 'bQ4wR8' does not resolve to an existing company",
  "instance": "/acme/api/v1/en/person"
}

The document member by member, what to do about each status code, and a retry policy worth copying are on Errors and troubleshooting. The token endpoint is the exception described above: it fails the OAuth 2.0 way rather than with a problem document.

Shared errors — every endpoint

The resource pages list only the responses specific to an endpoint. On top of those, every endpoint can answer these four:

Status Meaning
401 Missing, expired or revoked token. Mint a fresh token and retry once.
403 The token lacks the scope this method requires, or it was minted for another instance.
404 Unknown instance name in the URL.
500 Unexpected server error. Retry with exponential backoff, and report the problem body's timestamp if it persists.

An endpoint may give the same code a narrower meaning — most 404s are about the id in the path rather than the instance name. Where an endpoint documents a code itself, its own response table is the authority; the four above are documented once, in full, under Shared errors.