# 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.047687d6.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://docs.dinie.com/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 |
| 400 | `invalid_content_type` | `Content-Type` header is not `application/x-www-form-urlencoded` |
| 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: 1804118400,
});

// 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: 1804118400
)

# 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=1804118400,
)

# 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": 1804118400
  }'
```

### Response


```json
{
  "id": "dinie_ci_550e8400e29b41d4a716446655440000",
  "client_id": "dinie_ci_550e8400e29b41d4a716446655440000",
  "client_secret": "dinie_cs_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "name": "Production Key",
  "status": "active",
  "expires_at": 1804118400,
  "created_at": 1709546400,
  "updated_at": 1709546400,
  "last_used_at": null
}
```

> **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 (Unix epoch, seconds). `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": 1804118400,
      "created_at": 1709287200,
      "updated_at": 1709287200,
      "last_used_at": 1709544600
    }
  ],
  "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.