Skip to main content

Restricting a Personal Access Token

Scopes decide what a token may do. The controls on this page decide how far it may go — for how long, from where, on which accounts, and for how much money.

Expiry​

Set an expiry when you create the token: 30 days, 90 days, 1 year, a custom date up to 365 days ahead, or never. Once the expiry passes, every request with that token is answered 401 token_expired and the token shows as EXPIRED in your token list.

Prefer a real expiry over "never", even for long-lived automations. A token that expires is a leak with an end date.

Allowed IP addresses​

A token can be restricted to the addresses it is allowed to be used from. Give individual IP addresses or CIDR ranges, for example 203.0.113.7 or 203.0.113.0/24.

When a restriction is set, a request from any other address is refused with 403 forbidden — the token itself is still valid, it is simply not usable from there. Leave the list empty to accept requests from anywhere.

IPv6 ranges are not matched

The creation form accepts an IPv6 address and an IPv6 CIDR range, but the gateway matches a range for IPv4 only. An IPv6 range matches nothing, and every request from it is refused with 403 forbidden.

A bare IPv6 address does work, because the gateway compares it as text — which is also its limit. It is a text comparison, not an address comparison, so write the address exactly as your host presents it: lowercase, in the short form. 2001:0DB8::1 and 2001:db8:0:0:0:0:0:1 both fail against 2001:db8::1. If your traffic leaves over IPv6 from a changing address, list the individual addresses, or leave the allowlist empty and rely on the other controls.

Check the address you actually egress from

The address that matters is the public IP the request leaves from, not the address of the machine running the code. Behind NAT, a container platform or a corporate proxy those differ, and a proxy pool may egress from several addresses — list the whole range.

Account restriction​

A token's authorisation is a list of scope-and-account pairs, not a single account setting. When you create a token you pick the scopes and, optionally, one or more accounts; the token then carries one entry per scope-and-account combination. Pick accounts:read and transfers:read together with two accounts, and the token gets four entries.

The consequence: choosing accounts restricts every scope on that token to those accounts, and a request against any other account is refused, even an account you own. Leaving the account list empty produces unrestricted entries — each scope may then be used on any account your user is permitted to access.

This is the cheapest way to keep a script that watches one account from being able to touch the rest.

Spending limits​

Spending limits cap the money a token may move, independently of the scopes it holds. Two limits are available and they can be combined:

LimitMeaning
per_transaction_limitThe largest single transfer the token may make
daily_aggregate_limitThe total the token may move in one day, Europe/Vilnius time

Limits apply only to a token that carries the transfers:create scope. A token that cannot create transfers cannot move money, so there is nothing to cap.

Setting the limits​

Set the limits when you create the token. They are fields in the creation form at bank.paysera.com/en/personal-access-tokens, and the interface applies them for you, in EUR.

Mechanically, the interface does that by calling the API below with the token it has just created. You can make the same call yourself with a token you already hold — that is the recovery path when the interface's own attempt is refused. A limit is always written by the token it applies to; there is no way to set a limit on another token.

Set the limits at creation, or not at all

The limit record can be written once and there is no operation to update or delete it. That has two consequences, and the second one is a security property:

  • A wrong amount is permanent for the life of the token. The only correction is to revoke the token and create a new one.
  • A token that holds transfers:create and has no limit record can write its own. Anyone holding a leaked token can therefore claim the record first — with a limit of their choosing — and you can no longer apply yours, because the second write is rejected. If that happens, revoke the token.

Both are avoided by filling the limit fields in the creation form.

An allowlist can stop the interface writing the limit

The interface writes the limit with the new token, so the token's own allowed-IP list applies to that write. If you create the token from one machine and restrict it to the address of another, the write is refused and the interface tells you the token was created but its spending limit was not applied. You are left with a token that has no cap and an open write-once slot.

Create the token from an address that is on its allowlist, or add the address you create it from. Then confirm with the GET below that the record exists.

A token with no limit record answers the GET with 403, not 404. On this page that is the easy one to misread, because the other 403 you meet here is the allowed-IP refusal. Tell them apart by the body: the gateway's IP refusal carries error and message, while this one comes from the service and carries error and error_description.

If the record is absent, the write-once slot is still open and you hold the token, so send the POST below yourself from a host that is on the allowlist — usually the host the token was made for. Revoke and start again only when you cannot reach an allowed address, or when enough time has passed that somebody else may have claimed the slot.

curl -sS -X POST "https://api.paysera.com/public/rest/v1/token-spending-limits" \
-H "Authorization: Bearer $PAYSERA_PAT" \
-H "Content-Type: application/json" \
-d '{
"user_id": 123456,
"per_transaction_limit": { "amount": "50000", "currency": "EUR" },
"daily_aggregate_limit": { "amount": "200000", "currency": "EUR" }
}'
FieldRequiredNotes
user_idYesYour own Paysera user ID — the psr:u claim in the token's payload, see What is inside the token. A different user's ID is refused.
per_transaction_limitSee noteMoney object. Must be positive.
daily_aggregate_limitSee noteMoney object. Must be positive.

Each limit is optional on its own, but at least one of the two must be present. A body carrying only user_id is refused with 400 Bad Request and the message "At least one limit (per_transaction_limit or daily_aggregate_limit) must be provided".

The creation form in the interface always sends EUR; the API itself takes the currency from the money object, and transfers in other currencies are converted before they are measured against it.

Amounts are in minor units

amount is a string in the currency's minor units — "50000" with currency EUR means 500.00 EUR, not 50 000 EUR. This differs from the account balance endpoints, which report amounts in major units ("1250.00"). Getting this wrong sets a limit 100× off in either direction.

The response echoes the stored limits together with the token they belong to:

{
"jti": "84923f0a-1c7e-4d55-9a1f-77b0a1f0d9c2",
"user_id": 123456,
"created_at": 1755500000,
"per_transaction_limit": { "amount": "50000", "currency": "EUR" },
"daily_aggregate_limit": { "amount": "200000", "currency": "EUR" }
}

Changing or removing the limits​

You cannot. A token has at most one spending-limit record, the API exposes no update and no delete operation for it, and posting a second record for the same token returns 409 Conflict with the error code token_spending_limit_exists.

To change a cap, revoke the token and create a new one with the amounts you want. Plan for this when you write the amounts down — they are in minor units, so the common mistake is a factor of 100.

Reading the limits back​

curl -sS "https://api.paysera.com/public/rest/v1/token-spending-limits/84923f0a-1c7e-4d55-9a1f-77b0a1f0d9c2" \
-H "Authorization: Bearer $PAYSERA_PAT"

The path parameter is the token's jti, which is also the token ID shown in your token list.

How the limits are enforced​

Limits are checked while the transfer is being processed, not when the request is accepted. A transfer that breaches a limit is marked as failed and never leaves your account:

Failure codeCause
token_transaction_limit_exceededThe transfer amount is above per_transaction_limit
token_daily_limit_exceededThe transfer would push the day's total above daily_aggregate_limit

Two consequences worth planning for:

  • A 200 on the create call is not proof the transfer will go through. Read the transfer's status afterwards and handle these failure codes, the same way you handle any other transfer failure.
  • Currencies are converted before comparison. A transfer in USD is converted into the limit's currency at Paysera's rate before being measured against it, so a limit in EUR governs transfers in every currency.

The daily aggregate is counted per calendar day in Europe/Vilnius time, from midnight, over the transfers made with that same token. If your automation runs in another zone, the window it resets in is not local midnight — a job that runs late in the evening in the Americas is already on the next Vilnius day.

Suspending and revoking​

Both actions are performed from bank.paysera.com/en/personal-access-tokens:

  • Suspend — the token stops working immediately but keeps its configuration, and can be reactivated later. Use this the moment you suspect a leak, or to park a token you are not using.
  • Revoke — permanent. Use it when the automation that held the token is retired, or once you have rolled a replacement token into place.

A suspended or revoked token is rejected with 401 token_expired — the gateway reports every not-active token with that code, so check the token's status before you conclude the expiry passed. See Token errors.

A sensible default set​

For a script that prepares payments for you to approve:

  • scopes transfers:create and transfers:read, and no scope the script does not use
  • pinned to the one account it works on
  • 90-day expiry
  • allowed IPs limited to the host it runs on
  • a per_transaction_limit sized to a plausible payment and a daily_aggregate_limit sized to a plausible day

Set all of it in the creation form, in one go — and create the token from an address that is on the allowlist you are about to give it, or the limits will not be written. Every one of those is independently enforced, so a mistake in any single one does not become an incident on its own — and none of them can be edited afterwards, so the alternative to getting it right now is issuing a new token later.

Next steps​