Authentication
The VerityPay API uses Sanctum personal access tokens scoped to a tenant. Each token carries a set of fine-grained abilities that gate which endpoints it can hit. There is no OAuth flow — bring-your-own-token from the dashboard.
Mint a token
- Sign in to your tenant dashboard.
- Open
Settings → API Tokens → New Token. - Give the token a descriptive name (e.g.,
billing-service-prod,integrations-staging). Future-you will thank present-you when an old token leaks and you need to know which workload it belongs to. - Select the abilities the token needs (see Abilities below). Default to least-privilege.
- Copy the displayed token immediately — it is shown once and never again.
The token format looks like:
vp_pat_3|abc123...verylong...string
Treat it as a credential. Never check it in. Never paste it in a browser address bar. Always rotate when a developer leaves the team.
Send the token
Include the token on every request as a bearer credential:
GET /api/v1/contacts HTTP/1.1
Host: app.veritypay.com
Authorization: Bearer vp_pat_3|abc123...
Accept: application/json
The bearer token does two things at once:
- Resolves the tenant — the token is matched against the central
token_routestable, which sets the schema for the request before authentication runs. - Authenticates the caller — Sanctum then validates the token against that tenant's
personal_access_tokenstable.
You never send a tenant ID, slug, or schema header. The token is the tenant.
How abilities work
Abilities are slugs of the form api:{action}-{resource}. The controller calls $this->authorize($policyMethod, $resource), and the underlying gate accepts the call only when the token carries the matching ability.
The mapping is straightforward:
| Policy method | Token ability format | Example |
|---|---|---|
viewAny |
api:view-any-{resource} |
api:view-any-contact |
view |
api:view-{resource} |
api:view-contact |
create |
api:create-{resource} |
api:create-contact |
update |
api:update-{resource} |
api:update-contact |
delete |
api:delete-{resource} |
api:delete-contact |
restore |
api:restore-{resource} |
api:restore-contact |
Some lifecycle endpoints authorize against a non-CRUD policy method — for example, POST /payables/{id}/resend-notification calls authorize('resendNotification', $payable->transaction), which maps to the api:resend-transaction ability. The Endpoints reference lists the policy gate for every endpoint.
Abilities
The dashboard's New API Token form is the source of truth. As of the latest deploy, the assignable abilities are:
Contacts
api:view-any-contact
api:view-contact
api:create-contact
api:update-contact
api:delete-contact
api:restore-contact
Receivables
api:view-any-receivable
api:view-receivable
api:create-receivable
api:update-receivable
api:delete-receivable
api:restore-receivable
Payables
api:view-any-payable
api:view-payable
api:create-payable
api:update-payable
api:delete-payable
api:restore-payable
Transactions
api:view-any-transaction
api:view-transaction
api:view-token-transaction
api:create-transaction
api:update-transaction
api:delete-transaction
api:restore-transaction
api:resend-transaction
api:update-transaction is broad — it gates every payable/receivable lifecycle action that calls authorize('update', $transaction) (mark-paid, revoke-link, retry-payment, verify-payer, mark-disbursed, retry-disbursement, verify-identity, expire). Grant it deliberately.
Custom-field definitions
api:view-any-transaction-field-definition
api:view-transaction-field-definition
api:create-transaction-field-definition
api:update-transaction-field-definition
api:delete-transaction-field-definition
api:restore-transaction-field-definition
The contact-field-definition endpoints exist but their abilities are tenant-managed (not in the standard assignable set) — your tenant admin configures who can manage them.
Webhooks
api:view-any-webhook
api:view-webhook
api:create-webhook
api:update-webhook
api:delete-webhook
These gate the webhook-endpoint management endpoints. Note that api:update-webhook also covers rotate-secret — a token holding it can mint new signing secrets, so treat it as a credential-issuing grant.
Campaigns & messaging
api:view-any-campaign
api:view-campaign
api:create-campaign
api:update-campaign
api:delete-campaign
api:restore-campaign
api:assign-campaign
api:view-any-message
api:view-message
api:create-message
api:update-message
api:delete-message
api:restore-message
api:test-send-message
api:view-any-message-outbox
api:view-message-outbox
api:create-message-outbox
api:update-message-outbox
api:delete-message-outbox
api:restore-message-outbox
api:cancel-message-outbox
Opt-in & suppression
api:view-any-opt-in-code
api:view-opt-in-code
api:create-opt-in-code
api:update-opt-in-code
api:delete-opt-in-code
api:restore-opt-in-code
api:import-opt-in-code
api:view-any-suppression
api:create-suppression
api:delete-suppression
The opt-in endpoints are read-only and all three routes check api:view-any-opt-in-code; the remaining *-opt-in-code abilities exist for parity with the dashboard permissions and gate nothing over the API today. The suppression abilities gate the suppression list.
Other
api:view-payment-rail · api:update-payment-rail · api:delete-payment-rail
api:view-any-role · api:view-role · api:create-role · api:update-role · api:delete-role · api:restore-role
api:view-any-user · api:view-user · api:create-user · api:update-user · api:delete-user · api:restore-user
api:view-any-subscription · api:view-subscription · api:create-subscription · api:update-subscription · api:delete-subscription · api:restore-subscription
api:view-any-support-ticket · api:view-support-ticket · api:create-support-ticket · api:update-support-ticket · api:delete-support-ticket
api:view-any-tenant-config-meta · api:view-tenant-config-meta · api:create-tenant-config-meta · api:update-tenant-config-meta · api:delete-tenant-config-meta · api:restore-tenant-config-meta
The exact list available to your operator account depends on the permissions your dashboard role holds — the form intersects Permission::tenantAssignable() with the creator's own grants to prevent privilege escalation.
Recommended ability bundles
| Bundle | Abilities |
|---|---|
| Read-only reporting | api:view-any-contact, api:view-contact, api:view-any-payable, api:view-payable, api:view-any-receivable, api:view-receivable, api:view-any-transaction, api:view-transaction |
| Send payouts (claims/refunds bot) | api:create-payable, api:view-payable, api:delete-payable (for cancel), api:update-transaction (for revoke-link / retry / verify / expire), api:resend-transaction |
| Collect payments (invoice bot) | api:create-receivable, api:view-receivable, api:delete-receivable (for cancel), api:update-transaction (for mark-paid / revoke-link / retry / verify / expire), api:resend-transaction |
| Contact sync (CRM mirror) | api:create-contact, api:update-contact, api:view-any-contact, api:view-contact, api:delete-contact, api:restore-contact |
| Webhook self-management (ops bot) | api:view-any-webhook, api:view-webhook, api:create-webhook, api:update-webhook (for rotate-secret / re-enable), api:delete-webhook |
Token lifecycle best practices
- One token per service, never per human. Rotate when any operator with access leaves.
- Keep abilities minimal. A read-only reporting token cannot create payables — that's the whole point.
- Rotate proactively. Quarterly is a reasonable default; immediately on any suspected leak.
- Revoke unused tokens. The dashboard shows last-used timestamps; anything idle 90+ days should go.
- Store in a secret manager. Never in environment files committed to repos. Never in client-side code.
- Use separate tokens per environment. A staging token must not work in production — see the Sandbox → Production guide.
Authentication errors
| Status | error.code |
What it means |
|---|---|---|
| 401 | unauthenticated |
Token missing, malformed, expired, or revoked. |
| 401 | invalid_signature |
Signed-URL request had a bad/missing signature. |
| 403 | forbidden |
Token authenticated but lacks the required ability. |
A 403 forbidden after a 200 from the same token usually means a new endpoint shipped that gates on an ability you didn't grant. Mint a new token (or update the existing one) and retry.