Error codes
Rolink errors carry a stable, machine-readable code. Branch on the code, never on the message, which may change.
-
Over HTTP, the API answers with an error status and a JSON body:
{ "error": { "code": "amount_over_limit", "message": "Amount 5 exceeds the per-transaction limit of 0.1" } } -
In Luau, SDK methods raise a string, and
Rolink.errorCodeextracts the code:local ok, err = pcall(rolink.TransferSol, rolink, params)-- err = "[Rolink] amount_over_limit: Amount 5 exceeds the per-transaction limit of 0.1 (HTTP 403)"print(Rolink.errorCode(err)) -- "amount_over_limit"
For symptoms and step-by-step fixes, see Troubleshooting.
Retries
Section titled “Retries”The SDK tries a request up to 3 times, 0.5 and then 1.5 seconds apart, when:
- there was no HTTP answer (
network_error) - the status is 5xx, which includes
internal,rpc_errorandwallet_unavailable - the status is 429, except
recipient_rate_limited,player_rate_limited,hourly_budget_exceeded,daily_quota_exceededanddaily_tx_quota_exceeded, which are raised at once because an hourly or daily cap does not lift in a second. The per-minuterate_limitedis retried.
Every other code is raised on the first answer. See HTTP requests and retries.
General
Section titled “General”These can come from any /v1 route, and the last four from any route.
| Code | HTTP | Meaning | Fix |
|---|---|---|---|
unauthorized | 401 | The x-api-key header is missing, malformed, revoked, or belongs to a deleted project. | Check that the Roblox Secret holds the whole of an active key from the project’s API keys page, and that Rolink.new gets it as apiKey. |
rate_limited | 429 | The project made more than 600 requests in the current minute. | Cache and batch reads. Retried by the SDK. |
daily_quota_exceeded | 429 | The project used its 100,000 requests for the current UTC day. | Wait until 00:00 UTC, and find what calls so often in the dashboard’s usage view. Not retried by the SDK. |
invalid_json | 400 | The body is not valid JSON, or is missing on a route that needs one. | Send a JSON body. The SDK always does. |
invalid_request | 400 | The body does not match the route’s schema. The message lists each field and problem. | Read the message, such as amount: Invalid input: expected string, received number. |
not_found | 404 | No such route, or GET /v1/tx/:idempotencyKey has no request with that key in this project. | Check the path. For a key, check its spelling and URL encoding. |
internal | 500 | An unexpected error. Most often the Solana RPC failed during a read. | Retry. If it lasts, contact Rolink support. |
Wallets and reads
Section titled “Wallets and reads”| Code | HTTP | Meaning | Fix |
|---|---|---|---|
invalid_user_id | 400 | A user id is not a positive integer. From GET /v1/players/:userId/wallet. | Pass player.UserId. |
invalid_address | 400 | A value that must be a Solana address is not one: the wallet in a read path, mint or collection, to.address, a transaction’s mint, or the address in a linking challenge. | Check the base58 address. A numeric string passed as to is treated as an address, so pass a UserId as a number. |
invalid_mint | 400 | The mint does not exist on the project’s cluster, or the account is not a token mint. From token reads and token transactions. | Check the mint address, and that it lives on the project’s cluster (devnet or mainnet). |
invalid_signature | 400 | From GET /v1/signatures/:signature: not a transaction signature. From the linking page’s verify step: the signature is malformed or does not match the wallet. | Pass a base58 transaction signature. For linking, sign the exact message with the wallet named in the challenge. |
Transactions
Section titled “Transactions”Listed in the order Rolink checks them. See Order of checks. A request refused with any of these codes, except wallet_unavailable and rpc_error, does not reserve its idempotency key.
| Code | HTTP | Meaning | Fix |
|---|---|---|---|
wallet_not_configured | 409 | The project has no managed wallet yet. | Create it on the project’s Overview page in the dashboard, then fund it. |
idempotency_conflict | 409 | The key was already used in this project for a different request. Different means a different kind, recipient, mint or amount, as sent. { "userId": 42 } and that player’s address count as different, and so do "1" and "1.0". | Send the original request again unchanged, or use a new key for a new payment. |
wallet_not_linked | 404 | to is a UserId with no wallet linked to this project. | Ask the player to link a wallet first, or send to an address. |
sol_transfers_disabled | 403 | The policy has no Max SOL per transfer. | Set it under Transactions in the dashboard. |
mint_not_allowed | 403 | The mint is not under Allowed mints in the policy. | Add the mint with its maximum per transaction. |
invalid_amount | 400 | The amount is zero, or has more decimals than the asset allows (9 for SOL, the mint’s decimals for tokens). | Send an amount greater than zero, rounded to the asset’s decimals. |
amount_over_limit | 403 | The amount is above Max SOL per transfer, or above the mint’s maximum per transaction. | Send less, or raise the limit. |
invalid_limit_config | 422 | A stored limit or budget cannot be represented with the asset’s decimals, for example 0.5 on a 0-decimal mint. The dashboard normally refuses such values when you save. | Open the policy in the dashboard and save it again. |
daily_tx_quota_exceeded | 429 | The project already has 1,000 transactions in the last 24 hours. Failed ones do not count. | Wait, then retry with the same key. Not retried by the SDK. |
recipient_rate_limited | 429 | The recipient wallet already received the policy’s Per recipient wallet maximum in the last hour. Failed ones do not count. | Wait, or raise the limit. Not retried by the SDK. |
player_rate_limited | 429 | The recipient player already received the policy’s Per player maximum in the last hour, across all their wallets. | Wait, or raise the limit. Not retried by the SDK. |
hourly_budget_exceeded | 429 | This amount would take the asset past its hourly budget, across everyone. | Wait for the window to roll, or raise the budget if the spend is expected. Not retried by the SDK. |
wallet_unavailable | 502 | The managed wallet could not sign the transaction. The attempt is recorded as failed and nothing was sent. | Send again with the same key. Retried by the SDK. |
rpc_error | 502 | The Solana RPC could not be reached to build the transaction. The attempt is recorded as failed and nothing was sent. | Send again with the same key. Retried by the SDK. |
Linking
Section titled “Linking”These come from the routes behind each project’s linking page. Game servers never see them.
| Code | HTTP | Meaning | Fix |
|---|---|---|---|
project_not_found | 404 | No active project has the slug in the URL. The project may have been deleted. | Use the URL from GetProject().linkUrl or the dashboard’s Overview. |
bad_origin | 403 | A POST to /auth/logout, /auth/wallet/*, /api/link/* or a dashboard form had no Origin header, or one that is not the API’s origin. | Open the linking page or the dashboard directly, not through a proxy, translation service or embedded view. |
not_signed_in | 401 | No valid player session for this project. A session lasts one hour and only counts on the linking page of the game it was opened for. | Sign in with Roblox again on this game’s linking page. If it happens right after signing in, the browser is blocking cookies. |
challenge_expired | 400 | The nonce is unknown, already used, more than 10 minutes old, or was issued for another project or to another player. | Request a new challenge. A nonce is consumed by the first verify attempt, even a failed one. |
invalid_address and invalid_signature can also come from these routes. They are described under Wallets and reads.
Sign-in redirect errors
Section titled “Sign-in redirect errors”When Sign in with Roblox fails, Rolink does not answer with an error body. It redirects back to the linking page where the sign-in started, with ?error=<code>, and the page shows a message. An unknown code shows “Sign-in failed.” Players sign in through the game’s own Roblox OAuth app, so some of these are fixed in that app or in the project’s Settings → Roblox sign-in.
error | Shown on the linking page | Cause | Fix |
|---|---|---|---|
roblox_denied | Roblox sign-in was cancelled. | Roblox returned an error, usually because the user declined. | Nothing to fix. Start again. If every player gets it, check that the app allows the openid and profile scopes. |
oauth_state_mismatch | Sign-in expired. Please try again. | The sign-in cookie is missing or older than 10 minutes, there is no code, or state does not match. Without the cookie, Rolink can’t tell which linking page to return to, and redirects to /dashboard instead. | Start again from the linking page, in one browser, with cookies allowed. |
oauth_token_failed | Roblox sign-in failed. Please try again. | Roblox refused the code exchange, for example because the client secret saved in the project doesn’t match the app’s. | Paste the app’s client secret again in the project’s settings. Otherwise try again. |
oauth_userinfo_failed | Couldn’t read your Roblox profile. Please try again. | Roblox refused the profile request. | Try again. If it lasts, contact Rolink support. |
oauth_not_configured | This game hasn’t set up Roblox sign-in yet. | The project has no Roblox OAuth app saved: its client ID or client secret is missing. The linking page shows the same message without a redirect. | Save the app’s client ID and secret in the project’s Settings → Roblox sign-in. |
Dashboard sign-in
Section titled “Dashboard sign-in”These come from /auth/wallet/challenge and /auth/wallet/verify, which the dashboard’s sign-in page calls. The page shows the message.
| Code | HTTP | Meaning | Fix |
|---|---|---|---|
bad_origin | 403 | The request’s Origin header is not the API’s origin. | Open the dashboard directly. |
invalid_address | 400 | The wallet’s address is not a Solana address. | Choose another wallet. |
challenge_expired | 400 | No valid pending sign-in: the sign-in cookie is missing, older than 10 minutes, or was already used. | Choose your wallet again and sign within 10 minutes, with cookies allowed. |
invalid_signature | 400 | The signature is not 64 bytes (Malformed signature), or doesn’t match the wallet (The signature does not match this wallet). | Sign with the account the wallet connected. |
Dashboard
Section titled “Dashboard”These come from dashboard actions. The dashboard shows the message on the page, next to the form or as an error page, rather than the code; the codes are listed here for reference.
| Code | HTTP | Meaning | Fix |
|---|---|---|---|
invalid_settings | 400 | A setting, the policy or a watch list didn’t pass validation. The message lists each field and problem. | Fix the values named in the message. The rules are in Limits and quotas. |
project_not_found | 404 | The project doesn’t exist, was deleted, or belongs to another developer. | Sign in with the wallet that created it. |
wallets_unavailable | 503 | Create wallet was refused because managed wallets aren’t turned on for the hosted API yet: “Managed wallets aren’t set up on this server yet, so transactions are off.” Linking, reads, live events and the rest of the dashboard work. | Try again later. Until the project has a wallet, transaction calls answer wallet_not_configured. |
project_limit_reached | 403 | You already have 5 projects, the beta’s limit per developer. | Delete a project you no longer use. |
key_limit_reached | 403 | The project already has 10 active API keys. | Revoke a key before creating another. |
key_not_found | 404 | The key to revoke is already revoked, or isn’t in this project. | Reload the API keys page. |
Local development only
Section titled “Local development only”invalid_name (400) and an invalid_user_id from /auth/dev come from a development-only sign-in route that does not exist on the hosted API.
SDK errors
Section titled “SDK errors”These are raised by the SDK itself, not returned by the API.
| Code | Raised as | Meaning | Fix |
|---|---|---|---|
network_error | [Rolink] network_error: <HttpService error> | HttpService:RequestAsync got no HTTP answer on the last of its 3 attempts. | Check that HTTP requests are enabled for the experience and that the Secret’s domain is the Rolink API’s host. Leave apiUrl unset. |
http_<status> | [Rolink] http_502: <status message> (HTTP 502) | An HTTP error without a Rolink error body, typically from something between your server and the API. 5xx and 429 are retried first. | Retry later. Check that apiUrl is unset. |
Assertion errors
Section titled “Assertion errors”Some mistakes are caught before any request is sent. They raise a plain message without a code, so Rolink.errorCode returns nil:
| Message | Cause |
|---|---|
[Rolink] Rolink runs on the server only; never expose your project API key to clients | Rolink.new was called on a client. |
[Rolink] Rolink.new expects a config table | config is not a table. |
[Rolink] config.apiKey is required (use HttpService:GetSecret) | apiKey is nil. |
[Rolink] config.apiUrl must be a string | apiUrl is set to something other than a string. |
[Rolink] `to` must be a UserId (number) or a Solana address (string) | to is neither a number nor a string. |
[Rolink] amount must be a positive number or string | amount is a negative number, NaN, or another type. |
