Create your API key
How to obtain, rotate, and store the credentials used to authenticate
Octopus Cards uses long-lived API key / API secret pairs to mint short-lived JWTs. The JWTs authenticate individual API requests; the key/secret pair authenticates you to the login endpoint. Two distinct layers, two different lifetimes.
Getting your credentials
API keys are issued per client account by your onboarding contact. For a sandbox account:
- Request access through your account manager.
- Receive an email with your sandbox API key + API secret.
- Store both in your secrets manager (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, etc.) - never in source control, never in a
.env.example, never in CI logs.
Production credentials are issued separately after KYC/KYB - see Moving to Production.
Treat the secret like a password
Anyone with your API key + secret can mint access tokens and place orders against your wallets. If either leaks, rotate immediately - contact support for a replacement pair.
How credentials are used
Every authenticated request to the Octopus Cards API requires a JWT access token in the Authorization header:
curl https://api.octopuscards.io/api/v1/products \
-H "Authorization: Bearer <access_token>"That access token is obtained by exchanging the long-lived credentials at the login endpoint:
curl -X POST https://api.octopuscards.io/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "your_api_key",
"password": "your_api_secret"
}'The response contains an access token (valid 1 hour) and a refresh token (valid 7 days). Keep using the access token for requests until it expires, then use the refresh token to get a new pair.
Full details: Authentication reference.
Handling credentials in code
The right shape depends on your stack, but two rules are universal:
- Load from environment or secrets manager at boot. Never inline them.
- Never log them. Redact the
username/passwordfields before any structured logging.
A minimal example:
package client
import "os"
type Credentials struct {
APIKey string
APISecret string
BaseURL string
}
func LoadCredentials() Credentials {
return Credentials{
APIKey: os.Getenv("API_KEY"),
APISecret: os.Getenv("API_SECRET"),
// e.g. https://sandbox-api.octopuscards.io
BaseURL: os.Getenv("BASE_URL"),
}
}Rotation
Rotate your API secret in these cases:
- Suspected leak (unexpected charges, unauthorized login attempts, or a committed
.env). - Staff departure where the person had access.
- Scheduled rotation - at least once every 12 months is a sensible default.
Rotation flow:
- Request a new secret from your account manager.
- Deploy the new secret to your production environment.
- Confirm the new secret works by making a login call.
- Notify support to revoke the old secret.
Because JWTs are short-lived, you don't need to invalidate any in-flight tokens - they'll expire naturally within the hour.
IP allowlisting (optional, recommended for production)
Your client account can be restricted to a set of CIDR ranges. Requests from any other IP return 403 Forbidden:
{
"error": {
"name": "ForbiddenError",
"code": "FORBIDDEN",
"message": "IP address not authorized"
}
}Share your production egress IPs with your account manager to configure this. See Security for the full threat model.
Sandbox vs production keys
| Property | Sandbox | Production |
|---|---|---|
| Issued when | On account creation | After KYC/KYB + funding |
| Scope | Sandbox only | Production only |
| Can they be swapped? | No - different hosts will reject mismatched keys | No |
| Default IP allowlist | Off | Recommended: on |
Keys from one environment will not authenticate against the other. Use separate env vars (API_KEY_SANDBOX, API_KEY_PROD) to avoid mix-ups in code.
Next
- Your first order - end-to-end walkthrough using your new credentials.
- Authentication reference - every endpoint, header, and error in the auth flow.