Reference

API reference

Base URL https://api.odinpayroll.com (sandbox: https://api.sandbox.odinpayroll.com). Authenticate with a partner API key as a Bearer token. Generated from openapi.json, which the API's CI keeps in step with the code.

API keys

GET/v1/partners/{partnerId}/api-keys

List partner API keys

Responses: 200, 400, 401, 403, 404, 409

POST/v1/partners/{partnerId}/api-keys

Create a partner API key (shown once)

Body fieldTypeRequiredNotes
namestringyes
expiresInDaysinteger

Responses: 201, 400, 401, 403, 404, 409

DELETE/v1/partners/{partnerId}/api-keys/{tokenId}

Revoke a partner API key

Responses: 200, 400, 401, 403, 404, 409

Audit

GET/v1/audit

Audit entries for the active context (x-odin-context), newest first

Parameters: limit (query), cursor (query)

Responses: 200, 400, 401, 403, 404

GET/v1/email-audit

Outbound email captured for the active context (x-odin-context: global or partner:<id>)

Parameters: limit (query), cursor (query)

Responses: 200, 400, 401, 403, 404

Authentication

POST/v1/auth/login

Verify email and password

Body fieldTypeRequiredNotes
emailstringyesemail
passwordstringyes

Responses: 200, 400, 401, 429

POST/v1/auth/mfa/totp

Complete sign-in with an authenticator code

Body fieldTypeRequiredNotes
ticketstringyes
codestringyespattern ^\d{6}$

Responses: 200, 400, 401, 429

POST/v1/auth/mfa/email-otp/send

Email a one-time sign-in code

Body fieldTypeRequiredNotes
ticketstringyes

Responses: 200, 400, 401, 429

POST/v1/auth/mfa/email-otp

Complete sign-in with an emailed code

Body fieldTypeRequiredNotes
ticketstringyes
codestringyespattern ^\d{6}$

Responses: 200, 400, 401, 429

POST/v1/auth/magic-link

Email a sign-in link

Body fieldTypeRequiredNotes
emailstringyesemail

Responses: 202, 400, 401, 429

POST/v1/auth/magic-link/consume

Use a sign-in link

Body fieldTypeRequiredNotes
tokenstringyes

Responses: 200, 400, 401, 429

POST/v1/auth/recovery

Email a password reset link

Body fieldTypeRequiredNotes
emailstringyesemail

Responses: 202, 400, 401, 429

POST/v1/auth/recovery/complete

Set a new password from a reset link

Body fieldTypeRequiredNotes
tokenstringyes
passwordstringyes

Responses: 200, 400, 401, 429

POST/v1/auth/invites/accept

Accept an invitation

Body fieldTypeRequiredNotes
tokenstringyes
namestringyes
passwordstring

Responses: 200, 400, 401, 429

POST/v1/auth/logout

End the current session

Responses: 200, 401, 403, 404

Billing

GET/v1/partners/{partnerId}/billing

Billing method and status

Responses: 200, 400, 401, 403, 404, 502

PUT/v1/partners/{partnerId}/billing

Set the billing method (Platform Operator)

Body fieldTypeRequiredNotes
methodstringyesone of stripe, odin_osa
osaEndpointsobject
osaEndpoints.employeesstring
osaEndpoints.entitiesstring
osaEndpoints.platformstring
osaTokensobject
osaTokens.employeesstring
osaTokens.entitiesstring
osaTokens.platformstring

Responses: 200, 400, 401, 403, 404, 502

POST/v1/partners/{partnerId}/billing/stripe/session

Start a Stripe page to add a payment method or manage billing

Body fieldTypeRequiredNotes
kindstringyesone of setup, portal

Responses: 200, 400, 401, 403, 404, 502

GET/v1/partners/{partnerId}/usage

Month-to-date usage: employees paid, payroll entities, platform seat

Responses: 200, 400, 401, 403, 404, 502

Branding

GET/v1/branding

Public branding for a host, used before sign-in

Parameters: host (query)

Responses: 200

GET/v1/partners/{partnerId}/branding

Get partner branding

Responses: 200, 400, 401, 403, 404

PUT/v1/partners/{partnerId}/branding

Set partner branding. Colours failing WCAG AA contrast are rejected with the measured ratio.

Body fieldTypeRequiredNotes
productNamestringyes
logoUrlstring
faviconUrlstring
colorsobjectyes
colors.accentstringyespattern ^#[0-9A-Fa-f]{6}$
colors.accentTextstringyespattern ^#[0-9A-Fa-f]{6}$
colors.accentOnDarkstringyespattern ^#[0-9A-Fa-f]{6}$
colors.emberCtastringyespattern ^#[0-9A-Fa-f]{6}$
colors.chromestringyespattern ^#[0-9A-Fa-f]{6}$
supportEmailstringemail
supportUrlstring
termsUrlstring
privacyUrlstring

Responses: 200, 400, 401, 403, 404

Me

GET/v1/me

The signed-in person, their memberships and staff state

Responses: 200, 401, 403, 404

GET/v1/me/contexts

Contexts the person can switch between

Responses: 200, 401, 403, 404

GET/v1/me/context

Scopes and feature toggles for the active context (x-odin-context header)

Responses: 200, 401, 403, 404

POST/v1/me/totp/setup

Start authenticator app enrollment

Responses: 200, 401, 403, 404

POST/v1/me/totp/confirm

Confirm authenticator app enrollment

Body fieldTypeRequiredNotes
codestringyespattern ^\d{6}$

Responses: 200, 400, 401, 403, 404

GET/v1/me/tokens

List personal access tokens

Responses: 200, 401, 403, 404

POST/v1/me/tokens

Create a personal access token (shown once)

Body fieldTypeRequiredNotes
namestringyes
expiresInDaysinteger

Responses: 201, 401, 403, 404

DELETE/v1/me/tokens/{tokenId}

Revoke a personal access token

Responses: 200, 401, 403, 404

GET/v1/me/notifications

In-app notifications, newest first

Parameters: limit (query), cursor (query)

Responses: 200, 400, 401, 403, 404

POST/v1/me/notifications/{notificationId}/read

Mark one notification read (`all` marks every one)

Responses: 200, 400, 401, 403, 404

Members

GET/v1/partners/{partnerId}/members

List partner administrators

Parameters: limit (query), cursor (query)

Responses: 200, 400, 401, 403, 404, 409

POST/v1/partners/{partnerId}/invites

Invite a partner administrator

Body fieldTypeRequiredNotes
emailstringyesemail
rolestringyes

Responses: 202, 400, 401, 403, 404, 409

DELETE/v1/partners/{partnerId}/members/{personId}

Remove a partner administrator

Responses: 200, 400, 401, 403, 404, 409

GET/v1/organizations/{organizationId}/members

List organization administrators

Parameters: limit (query), cursor (query)

Responses: 200, 400, 401, 403, 404, 409

POST/v1/organizations/{organizationId}/invites

Invite an organization administrator

Body fieldTypeRequiredNotes
emailstringyesemail
rolestringyes

Responses: 202, 400, 401, 403, 404, 409

DELETE/v1/organizations/{organizationId}/members/{personId}

Remove an organization administrator

Responses: 200, 400, 401, 403, 404, 409

Organizations

GET/v1/partners/{partnerId}/organizations

List a partner's organizations

Parameters: limit (query), cursor (query)

Responses: 200, 400, 401, 403, 404, 409

POST/v1/partners/{partnerId}/organizations

Create an organization

Body fieldTypeRequiredNotes
namestringyes

Responses: 201, 400, 401, 403, 404, 409

GET/v1/organizations/{organizationId}

Get an organization

Responses: 200, 400, 401, 403, 404, 409

Partners

GET/v1/partners

List partners (Odin staff)

Parameters: limit (query), cursor (query)

Responses: 200, 400, 401, 403, 404, 409

POST/v1/partners

Create a partner (Platform Operator)

Body fieldTypeRequiredNotes
namestringyes
slugstringyespattern ^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$

Responses: 201, 400, 401, 403, 404, 409

GET/v1/partners/{partnerId}

Get a partner

Responses: 200, 400, 401, 403, 404, 409

GET/v1/partners/{partnerId}/features

Resolved feature toggles

Responses: 200, 400, 401, 403, 404, 409

PUT/v1/partners/{partnerId}/features

Set feature toggles (full map; omitted keys default on)

Body fieldTypeRequiredNotes
featuresobjectyes

Responses: 200, 400, 401, 403, 404, 409

Payroll entities

POST/v1/organizations/{organizationId}/payroll-entities

Create a payroll entity (one legal employer) in an organization

Parameters: idempotency-key (header)

Body fieldTypeRequiredNotes
legalNamestringyes
doingBusinessAsstring
websitestring
adminobjectyesThe company's primary business user in the payroll engine (usually the owner).
admin.firstNamestringyes
admin.lastNamestringyes
admin.emailstringyesemail
admin.phonestringyespattern ^\d{10}$
termsAcceptedbooleanyesone of true

Responses: 200, 201, 400, 401, 403, 404, 409, 502

GET/v1/organizations/{organizationId}/payroll-entities

List an organization's payroll entities, with live details from the payroll engine

Parameters: limit (query), cursor (query)

Responses: 200, 400, 401, 403, 404, 409, 502

GET/v1/payroll-entities/{entityId}

Get a payroll entity

Responses: 200, 400, 401, 403, 404, 409, 502

Staff

POST/v1/staff/act-as

View the product as another person, read-only (Support, Super Admin)

Body fieldTypeRequiredNotes
personIdstringyespattern ^[A-Za-z0-9_-]{1,64}$
reasonstringyes

Responses: 200, 400, 401, 403, 404, 409

DELETE/v1/staff/act-as

Stop acting as someone

Responses: 200, 400, 401, 403, 404, 409

POST/v1/staff/elevations

Start a time-boxed break-glass elevation (Super Admin)

Body fieldTypeRequiredNotes
scopeTypestringyesone of partner, organization
scopeIdstringyespattern ^[A-Za-z0-9_-]{1,64}$
reasonstringyes
minutesintegeryes

Responses: 201, 400, 401, 403, 404, 409

GET/v1/staff/elevations

Active elevations across the platform

Responses: 200, 400, 401, 403, 404, 409

DELETE/v1/staff/elevations/current

End the current elevation early

Responses: 200, 400, 401, 403, 404, 409

GET/v1/staff/people

Find a person by email, with their memberships (Odin staff)

Parameters: email (query, required)

Responses: 200, 400, 401, 403, 404, 409

Webhooks and events

GET/v1/partners/{partnerId}/webhooks

List webhook endpoints

Responses: 200, 400, 401, 403, 404

POST/v1/partners/{partnerId}/webhooks

Add a webhook endpoint. The signing secret is returned once.

Body fieldTypeRequiredNotes
urlstringyes
eventsstring[]
descriptionstring

Responses: 201, 400, 401, 403, 404

DELETE/v1/partners/{partnerId}/webhooks/{subscriptionId}

Remove a webhook endpoint

Responses: 200, 400, 401, 403, 404

POST/v1/partners/{partnerId}/webhooks/{subscriptionId}/test

Send a signed test event now

Responses: 200, 400, 401, 403, 404

GET/v1/partners/{partnerId}/webhooks/{subscriptionId}/deliveries

Recent delivery attempts

Responses: 200, 400, 401, 403, 404

GET/v1/partners/{partnerId}/events

The partner's event log, newest first (for polling or replay)

Parameters: limit (query), cursor (query)

Responses: 200, 400, 401, 403, 404