Conventions
This page describes cross-cutting rules of the API. Read it once and it applies everywhere.
Document identifiers
Each DTE issued via the API has three identifiers, designed for different purposes:
| Identifier | Assigned by | Format | Typical use |
|---|---|---|---|
document_id | Ocote | UUID v4 | Internal API identifier. Returned in the response. |
external_ref | You | Free text (max 100 chars) | Identifier you control. Idempotency and retry. |
control_number | MH (fixed by Ocote) | DTE-{type}-{branch}{pos}-{15 digits} | Fiscal correlative. Appears in the signed JSON and the ticket. |
Important rules
external_refis unique per company. Two issuances with the same API key and sameexternal_refreference the same document. See Retry and idempotency.control_numberis never regenerated. Once assigned, it stays even across retries.- Endpoints that accept an
id_or_refaccept all three identifiers interchangeably — UUID or yourexternal_refboth work in/status/{id},/invalidate/{id},/file/{id}, etc.
external_ref is optional but strongly recommended
If you don't send external_ref, Ocote assigns one automatically derived from the UUID. It works, but you lose the ability to find the document from your side without storing the returned UUID, and you lose the safe-retry window after a network timeout.
Recommendation: generate your own external_ref with a readable convention (SALE-2026-000042, INV-A1B2C3, anything), store it before calling the API, and send it in every request.
HTTP status codes
The API follows standard REST conventions with one important nuance for issuance:
| Code | When | What to do |
|---|---|---|
200 OK | The operation was registered in Ocote. Could be success, contingency, or MH rejection. | Read the body flags. |
400 Bad Request | Payload validation error (missing field, malformed NIT, negative quantity). | Fix the payload. |
401 Unauthorized | API key missing, invalid, or company disabled. | Check the key. |
404 Not Found | Document not found by id_or_ref, or nonexistent route. | Verify the identifier. |
409 Conflict | Operation incompatible with current state (e.g. invalidating an already-invalidated document). | Query /status/{id} to see current state. |
429 Too Many Requests | Rate limit exceeded. | Wait and respect Retry-After. See Rate limits. |
500 Internal Server Error | Uncontrolled error in Ocote. | Retry after a few seconds; if it persists, contact Ocote. |
200 OK does not always mean "MH accepted"A 200 response with rejected: true means: Ocote registered your request correctly, but MH rejected the document due to recipient data. Correct and retry with the same external_ref.
See the full flags table in Responses and states.
Encoding and content-type
- Request: every POST must carry
Content-Type: application/jsonand a UTF-8 encoded body. - Response: UTF-8 JSON. Accents and Spanish characters do not require escaping.
- Downloaded files: ticket in
application/pdf, JSON inapplication/json(with the full JWS including MH stamp).
Dates and time zones
- All dates in responses are ISO 8601 with time zone. Ocote runs in
America/El_Salvador(UTC-6). Example:2026-04-21T10:42:15-06:00. - For date filters in listing/query endpoints, use
YYYY-MM-DDformat (e.g.date_from=2026-04-01). Interpreted in local time (UTC-6). - You do not send dates in issuance requests. Ocote assigns the posting timestamp at the moment it accepts the request.
Amounts, currency, and decimals
- Single currency: USD. The API does not accept or return amounts in any other currency. El Salvador operates in US dollars as legal tender for DTE.
- All unit prices are sent VAT-included for local sales (types 01, 03, 14). Ocote internally breaks down taxable base and 13% VAT.
- Decimals: accepts up to 2 decimals in amounts and up to 8 decimals in quantities (for fractional weight/volume units). Amounts are rounded to 2 decimals in the final DTE per MH rules.
- Do not send currency symbols or thousand separators. Correct:
100.00. Incorrect:"$100","1,000.00".
Enumerated values that aren't self-describing
Some MH fields use numeric codes that can't be inferred from context. The most frequent:
| Field | Values | Meaning |
|---|---|---|
doc_type | "01", "03", "05", "11", "14" | Invoice, CCF, Credit Note, Export Invoice, Excluded Subject. |
type_contributor | 1, 2, 3, 4 | Small, Medium, Large, Government. |
transaction_condition | 1, 2, 3 | Cash, Credit, Other. |
payment_method | "01", "02", "03", "05", "08", … | Cash, Debit Card, Credit Card, Transfer, Electronic Money. See MH catalogs. |
Optional fields vs omitted fields
The API distinguishes between absent field (you didn't send it) and field sent with empty value. Practical rule:
- If a field is optional and you don't need it, omit it. Don't send
"email": ""or"email": nullif you don't want an email; just don't include the key. - Explicit
nullvalues are accepted as equivalent to "omitted" in most fields, but some MH validators reject them. Prefer omitting.
Known bug: phone
The customer.phone field is currently not mapped correctly to the internal model and may trigger a validation error if sent. Workaround: omit the phone field from the customer object until the fix is deployed.
This bug does not affect issuance of any document, only the mapping of the recipient phone to the customer record in Ocote. MH does not require phone in any DTE type.
Versioning
The API is in its first public stable version. There is no version prefix in the URL (/api/connect/... not /api/connect/v1/...). Future changes follow these rules:
- Additive changes don't break. Adding new response fields or optional request parameters will never require client changes.
- Breaking changes are announced in advance and introduced under a dedicated
/api/connect/v2/path, keeping v1 during a deprecation period.
See Changelog for the change history.