Authentication
Every API Connect request is authenticated with an API key in the Authorization header using the Bearer scheme.
Authorization: Bearer odt_f57b5eabebb5ee8698d54c5c9427a991f6254150e819d04b2f56a490
There is no other authentication scheme. We don't use OAuth, we don't use sessions, and there is no automatic token rotation. An API key is long-lived until you deactivate it.
Obtaining an API key
API keys are issued by Ocote directly against a company configured in the platform. The flow is:
- You (or your customer) already has the company registered in Ocote DTE, with the MH signer loaded and at least one branch/POS active.
- Ocote generates the API key from the admin panel and hands it to you.
- You store it in your secret manager.
The key is associated with the company and does not expire by default, but can be deactivated at any time from the Ocote panel (the event is logged and any subsequent request returns 401).
Token format
API keys follow the format:
odt_[56 hexadecimal characters]
The odt_ prefix (Ocote DTE) is fixed and helps you spot them visually if you see them in logs or environment variables. A typical key:
odt_f57b5eabebb5ee8698d54c5c9427a991f6254150e819d04b2f56a490
How to send it
Standard header for every read/write request:
POST /api/connect/invoice HTTP/1.1
Host: ocote.io
Authorization: Bearer odt_xxx
Content-Type: application/json
Exception: file download endpoint.
The GET /file/{id} endpoint accepts the key as a query parameter instead of a header, because it is often used directly from the browser (open a ticket, share a JSON link):
GET /api/connect/file/{document_id}?type=ticket&key=odt_xxx
Everything else — issuance, query, invalidation — always uses the Authorization header.
Examples
curl https://ocote.io/api/connect/status/SALE-2026-0042 \
-H "Authorization: Bearer odt_f57b5eabebb5ee8698d54c5c9427a991f6254150e819d04b2f56a490"
import axios from 'axios';
const ocote = axios.create({
baseURL: 'https://ocote.io/api/connect',
headers: {
'Authorization': `Bearer ${process.env.OCOTE_API_KEY}`,
'Content-Type': 'application/json',
},
});
const { data } = await ocote.get('/status/SALE-2026-0042');
console.log(data);
import os, requests
headers = {
"Authorization": f"Bearer {os.environ['OCOTE_API_KEY']}",
"Content-Type": "application/json",
}
r = requests.get(
"https://ocote.io/api/connect/status/SALE-2026-0042",
headers=headers,
)
r.raise_for_status()
print(r.json())
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://ocote.io/api/connect/',
'headers' => [
'Authorization' => 'Bearer ' . getenv('OCOTE_API_KEY'),
'Content-Type' => 'application/json',
],
]);
$response = $client->get('status/SALE-2026-0042');
echo $response->getBody();
Authentication error responses
| HTTP | Message | Cause |
|---|---|---|
| 401 | API key requerida | Missing Authorization header or doesn't start with Bearer . |
| 401 | API key invalida | Key does not exist or was deactivated. |
| 401 | Empresa inactiva | Company bound to the key is disabled. |
401 responses do not count against rate limit. 400/500 responses don't count against the hourly limit either, but do count against the minute limit (see Rate limits).
Rotation and security
- Treat the key like a password. Don't expose it in the frontend, don't commit it to repos, don't send it over unencrypted channels.
- If compromised, request a new key from Ocote and deactivate the old one. Any subsequent request with the old key will return 401. Your
external_refs remain yours: issuances you made before stay queryable with the new key as long as they belong to the same company. - One key per environment is recommended. If you have stage and production, use two different keys — typically one on a test-environment company and another on a production-environment company.
The API Connect is meant to be consumed from a backend. If your frontend needs to display DTE info, the correct pattern is for your backend to query the API with the key and expose its own endpoint to the frontend.