# Authentication The Dinie API uses **OAuth2 Client Credentials** for authentication. You exchange your API credentials for a short-lived access token and include that token in all requests. ![Authentication flow](/assets/authentication-flow.2ebb7cebdf93d91a574051410895e4a8b21540b1c2887c001114e6473ef853b4.53e9d79f.svg) ## Overview 1. Your server sends credentials to `POST /v3/auth/token` 2. Dinie returns a short-lived JWT access token 3. Include the token as a Bearer header in all subsequent requests ## Obtaining a Token Send a `POST` request with your `client_id` and `client_secret` via HTTP Basic Authentication: ```typescript Node.js import Dinie from "dinie"; // The SDK exchanges credentials and caches the token automatically. const dinie = new Dinie({ clientId: process.env.DINIE_CLIENT_ID, clientSecret: process.env.DINIE_CLIENT_SECRET, environment: "sandbox", }); ``` ```ruby Ruby require "dinie" # The SDK exchanges credentials and caches the token automatically. dinie = Dinie::Client.new( client_id: ENV["DINIE_CLIENT_ID"], client_secret: ENV["DINIE_CLIENT_SECRET"], environment: "sandbox" ) ``` ```python Python from dinie import Dinie # The SDK exchanges credentials and caches the token automatically. dinie = Dinie( client_id=os.environ["DINIE_CLIENT_ID"], client_secret=os.environ["DINIE_CLIENT_SECRET"], environment="sandbox", ) ``` ```bash cURL curl -X POST https://sandbox.api.dinie.com.br/v3/auth/token \ -u "dinie_ci_your_client_id:dinie_cs_live_your_client_secret" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" ``` ### Response ```json { "access_token": "dinie_at_...", "token_type": "bearer", "expires_in": 3600 } ``` | Field | Description | | --- | --- | | `access_token` | JWT token prefixed with `dinie_at_`. Use as a Bearer token. | | `token_type` | Always `bearer`. | | `expires_in` | Token lifetime in seconds (3600 = 1 hour). | ## Using Bearer Tokens Include the token in the `Authorization` header of each API request: ```bash Authorization: Bearer dinie_at_... ``` > **Warning:** Never include credentials or tokens in URLs or query parameters. Always use the `Authorization` header. ## Token Expiration and Renewal Tokens expire after **1 hour** (3600 seconds). When a token expires, the API returns a `401` error: ```json { "type": "https://api.dinie.com.br/errors/authentication-failed", "title": "Authentication Failed", "status": 401, "detail": "Bearer token has expired.", "code": "token_expired" } ``` To handle expiration: 1. **Proactive renewal** -- track the `expires_in` and request a new token before it expires (e.g., at 80% of TTL) 2. **Reactive renewal** -- catch `401` responses with the `token_expired` code, request a new token, and resend the request > **Tip:** The SDKs manage token renewal automatically. They request a new token before the current one expires and transparently resend failed requests. ## Automatic SDK Authentication When you initialize the SDK with your credentials, it manages the entire token lifecycle: - Exchanges credentials for a token on the first API call - Caches the token in memory - Renews the token before expiration - Resends requests that failed due to expired tokens You never need to call the token endpoint directly when using an SDK. ## Authentication Errors | Status | Code | Description | | --- | --- | --- | | 400 | `missing_grant_type` | Missing `grant_type` parameter | | 400 | `unsupported_grant_type` | `grant_type` is not `client_credentials` | | 400 | `missing_authorization` | Missing `Authorization` header | | 401 | `invalid_client` | `client_id` does not exist | | 401 | `invalid_client_secret` | `client_secret` does not match | | 401 | `credential_revoked` | Credential has been revoked | | 401 | `credential_expired` | Credential is past its expiration date | ## Security Best Practices 1. **Store secrets securely** -- use environment variables or a secret manager. Never commit credentials to version control. 2. **Use server-side calls only** -- never expose your `client_secret` in frontend code, mobile apps, or client-side JavaScript. 3. **Rotate credentials regularly** -- create a new credential, update your integration, then revoke the old one. See the [Credential Management](#credential-management) section below. 4. **Separate credentials by environment** -- use different credentials for sandbox and production. 5. **Monitor usage** -- check the `last_used_at` field on your credentials to detect unauthorized use. # Credential Management Partners can have **multiple active API credentials** simultaneously. This enables credential rotation without downtime -- create a new credential, update your integration, then revoke the old one. ## Creating New Credentials Create a new API key with a human-readable name and an optional expiration date. ```typescript Node.js const credential = await dinie.auth.credentials.create({ name: "Production Key", expires_at: "2027-03-04T00:00:00Z", }); // Store credential.client_secret securely -- it is shown only once. console.log(credential.client_id); console.log(credential.client_secret); ``` ```ruby Ruby credential = dinie.auth.credentials.create( name: "Production Key", expires_at: "2027-03-04T00:00:00Z" ) # Store credential.client_secret securely -- it is shown only once. puts credential.client_id puts credential.client_secret ``` ```python Python credential = dinie.auth.credentials.create( name="Production Key", expires_at="2027-03-04T00:00:00Z", ) # Store credential.client_secret securely -- it is shown only once. print(credential.client_id) print(credential.client_secret) ``` ```bash cURL curl -X POST https://sandbox.api.dinie.com.br/v3/auth/credentials \ -H "Authorization: Bearer dinie_at_..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: create-prod-key-2026" \ -d '{ "name": "Production Key", "expires_at": "2027-03-04T00:00:00Z" }' ``` ### Response ```json { "id": "dinie_ci_550e8400e29b41d4a716446655440000", "client_id": "dinie_ci_550e8400e29b41d4a716446655440000", "client_secret": "dinie_cs_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "name": "Production Key", "status": "active", "expires_at": "2027-03-04T00:00:00Z", "created_at": "2026-03-04T10:00:00Z", "updated_at": "2026-03-04T10:00:00Z" } ``` > **Warning:** The `client_secret` is **returned only once** at creation time. Store it immediately in your secret manager. It cannot be retrieved again. | Field | Required | Description | | --- | --- | --- | | `name` | Yes | Human-readable label for this key (e.g., "Production Key", "Staging Key") | | `expires_at` | No | Optional expiration date (ISO 8601). `null` means the credential never expires. | ## Listing Credentials Retrieve all credentials for your account. Secrets are never included in list responses. ```typescript Node.js const credentials = await dinie.auth.credentials.list(); for (const cred of credentials.data) { console.log(cred.name, cred.status, cred.last_used_at); } ``` ```ruby Ruby credentials = dinie.auth.credentials.list credentials.data.each do |cred| puts "#{cred.name} #{cred.status} #{cred.last_used_at}" end ``` ```python Python credentials = dinie.auth.credentials.list() for cred in credentials.data: print(cred.name, cred.status, cred.last_used_at) ``` ```bash cURL curl https://sandbox.api.dinie.com.br/v3/auth/credentials \ -H "Authorization: Bearer dinie_at_..." ``` ### List Response ```json { "data": [ { "id": "dinie_ci_550e8400e29b41d4a716446655440000", "client_id": "dinie_ci_550e8400e29b41d4a716446655440000", "name": "Production Key", "status": "active", "expires_at": "2027-03-04T00:00:00Z", "created_at": "2026-03-01T10:00:00Z", "last_used_at": "2026-03-04T09:30:00Z" } ], "has_more": false } ``` ## Revoking Credentials Revoke a credential by `client_id`. Revocation takes effect immediately -- no new tokens can be issued with this credential. Already-issued tokens continue working until they expire (up to 1 hour). ```typescript Node.js await dinie.auth.credentials.del("dinie_ci_550e8400e29b41d4a716446655440000"); ``` ```ruby Ruby dinie.auth.credentials.delete("dinie_ci_550e8400e29b41d4a716446655440000") ``` ```python Python dinie.auth.credentials.delete("dinie_ci_550e8400e29b41d4a716446655440000") ``` ```bash cURL curl -X DELETE https://sandbox.api.dinie.com.br/v3/auth/credentials/dinie_ci_550e8400e29b41d4a716446655440000 \ -H "Authorization: Bearer dinie_at_..." ``` The response is `204 No Content` with an empty body. > **Info:** You cannot revoke your **last active credential**. A partner must always have at least one active credential. Attempting to revoke the last one returns a `409 Conflict` with code `last_active_credential`. ## Rotation Best Practices Follow this process for zero-downtime credential rotation: 1. **Create a new credential** with `POST /v3/auth/credentials` 2. **Update your integration** to use the new `client_id` and `client_secret` 3. **Verify the new credential works** by requesting a token with `POST /v3/auth/token` 4. **Revoke the old credential** with `DELETE /v3/auth/credentials/{old_client_id}` During steps 1--4, **all active credentials work simultaneously**. There is no authentication downtime. > **Tip:** Set an `expires_at` on credentials as a safety net. Even if you forget to revoke an old credential, it will stop working after the expiration date. ### Recommended Rotation Frequency | Environment | Rotation Frequency | | --- | --- | | Production | Every 90 days | | Staging | Every 30 days or on demand | Monitor the `last_used_at` field to detect credentials that are no longer in use and should be revoked.