The Cannis API: your organization and your account
The API does two things. An organization that uses Cannis OS receives the membership requests people fill in on its own website, without sending anyone to another page: two open lookups —provinces and localities— and the submission of the request, with a token issued to the organization. And a person manages their Cannis account from an app: they sign up, sign in, see their data and their organizations, and get into Cannis Pro.
01 Before you start
Every request that arrives through the API goes into Cannis OS, under «Solicitudes de afiliación» (membership requests) of your organization, marked as coming from its website, and the organization gets an email notice. From there it is approved or rejected like any other.
- Access is requested. Cannis sets it up for your organization and hands you the token together with the final field names.
- The request is sent from your website's server. The form the person sees talks to your server, and your server talks to Cannis: the token never reaches the browser.
- The reCAPTCHA is solved in the form. The person solves it on your site and your server sends the result along with the request.
02 Base URL and format
Everything hangs from the /api prefix and answers JSON. It is a stateless API: no cookies, no session. Always send the Accept: application/json header: without it, some errors —such as too many requests— may come back as a web page instead of JSON.
Not every response of your organization's API has the same shape: the two lookups return an array and the submission returns an object with its status. Each section shows its own. Your account API lives under /api/v1 and always answers with the same envelope: ok, mensaje, datos and errores.
https://ns1.cannis.org/api
The two open lookups accept up to 60 requests per minute from the same IP. If you use them to fill a dropdown, cache the response on your site: provinces and localities hardly ever change.
03 Authentication
Provinces and localities are open. The submission is not: it carries a token issued per organization, sent in the Authorization header. The token identifies the organization, so there is no need —and no way— to say which one it is: the request is tied to the token's organization.
Authorization: Bearer <tu-token> Accept: application/json
- A token may or may not have an expiry date.
- A revoked token stops working instantly.
- Every use of the token is logged.
- The token is secret: it lives on your website's server, never in code that runs in the browser. Whoever has it can submit requests on behalf of your organization; if it leaked, ask for it to be revoked.
04 Provinces
Returns the Argentine provinces with the identifier the membership request expects later. No parameters. The response is an array.
[
{ "id_provincia": 1, "nombre": "Buenos Aires" },
{ "id_provincia": 2, "nombre": "Catamarca" }
]
curl -H "Accept: application/json" https://ns1.cannis.org/api/provincias
05 Localities
The localities of a province: the classic second dropdown. The response is an array.
id_provincia returned by the provinces lookup. It must exist.
[
{ "id": 1042, "nombre": "Rosario" },
{ "id": 1043, "nombre": "Venado Tuerto" }
]
If id_provincia is missing or does not exist, the errors come per field:
{
"message": "Parámetros inválidos",
"errors": {
"id_provincia": [ "El campo id provincia es obligatorio." ]
}
}
curl -H "Accept: application/json" "https://ns1.cannis.org/api/localidades?id_provincia=21"
Keep the id: it is what later goes in the localidad field of the membership request, just as id_provincia goes in provincia.
06 Membership request
It submits the membership form an organization publishes on its own website: the person fills in their details there, your server sends them and the request enters Cannis OS, under «Solicitudes de afiliación» (membership requests) of your organization.
It is sent as multipart/form-data, because it accepts files, and from your server. It is protected with reCAPTCHA: the person solves it in the form and your server sends the result.
The field names on this page describe what data the request asks for, not exactly how they are named in the request body. We give you the final names together with the token, when we set up your organization's access.
YYYY-MM-DD.
id_provincia from the provinces lookup.
id from the localities lookup.
curl -X POST https://ns1.cannis.org/api/post/solicitud_paciente \ -H "Authorization: Bearer <tu-token>" \ -H "Accept: application/json" \ -F "nombre=Lucía" \ -F "apellido=Fernández" \ -F "documento=30111222" \ -F "cuit=27-30111222-4" \ -F "fecha_nacimiento=1983-04-12" \ -F "[email protected]" \ -F "celular=3415551234" \ -F "provincia=21" \ -F "localidad=1042" \ -F "calle=San Martín" \ -F "numero=1234" \ -F "reprocann=1" \ -F "consentimiento=1" \ -F "captcha=<resultado-del-recaptcha>" \ -F "[email protected]"
{
"status": "success",
"message": "Tu solicitud ha sido enviada exitosamente."
}
07 Errors
Submission errors carry "status": "error". Validation errors come in messages, already written in Spanish with the readable field name: they can be shown as they are next to the form.
{
"status": "error",
"messages": [
"Email: Ya existe un registro con ese Email en esta ONG.",
"Documento: El Documento debe contener entre 8 y 9 números."
]
}
If the reCAPTCHA does not verify, the 422 carries errors —an object with the field and its messages— instead of messages:
{
"status": "error",
"errors": {
"captcha": [ "Error en la verificación de reCAPTCHA." ]
}
}
Without a token, or with one that does not work, the response is an object with status and message:
{
"status": "error",
"message": "Token API no proporcionado."
}
Authorization header is missing or does not start with Bearer.
Accept: application/json it comes as {"message": "Too Many Attempts."}, with the Retry-After header: the seconds to wait.
Does your organization want to receive requests from its website? Request access on WhatsApp: Cannis sets it up for your organization. If it is not on Cannis yet, add it first.
08 Your account API
It is the API the Cannis app uses, and it is open to any account: sign up, sign in, see and change one's own data, know which organizations the person operates and get into Cannis Pro. One single account, the same as on the website and in every product. The field names are the real ones.
https://ns1.cannis.org/api/v1
Every response carries the same envelope. ok says whether it worked; mensaje is a text in Spanish that can be shown; datos is what was asked for and errores, on a 422, the messages per field.
{ "ok": true, "mensaje": "Listo.", "datos": { "…": "…" }, "errores": null }
{ "ok": false, "mensaje": "Qué pasó, en claro", "datos": null, "errores": { "campo": [ "mensaje" ] } }
Using this API requires an application key: it identifies the app, not the person, and Cannis issues it on request. It goes in the X-Cannis-App header of every request, including sign-up and sign-in; without it the response is 401. A key can be revoked on the spot, and every call is logged on our side.
Sign-up and sign-in return a token that identifies the person and the device. It travels in every request as Authorization: Bearer. It expires after 90 days without use and every use renews it; changing the password closes every other session. One token per device: dispositivo is the name the app gives it («iPhone de Ana»).
X-Cannis-App: <clave-de-aplicación> Authorization: Bearer <token-de-usuario> Accept: application/json
Creates the account and returns the first token. Up to five attempts per minute from the same IP.
password_confirmation.YYYY-MM-DD, before today.{
"ok": true, "mensaje": "Cuenta creada.",
"datos": {
"token": "12|Qx9…",
"vence": "2027-01-29T14:05:00-03:00",
"usuario": { "…": "los mismos datos de GET /api/v1/usuario" }
},
"errores": null
}
The account is created in Cannis and, at once, in Cannis Pro with the same account number. An email that already exists answers 422 with errores.email.
Email and password, and it returns a token with the same shape as sign-up. Wrong credentials: 422 with errores.email.
curl -X POST https://ns1.cannis.org/api/v1/ingresar \
-H "X-Cannis-App: <clave-de-aplicación>" \
-H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"…","dispositivo":"iPhone de Ana"}'
The full account data: the profile, the organizations the person operates with the role they would have in Cannis Pro and whether that organization has a plan, the NGOs they are a member of (without any health data) and which products they can enter today.
{
"ok": true, "mensaje": "Listo.",
"datos": { "usuario": {
"id": 5120, "nombre": "Ana", "apellido": "Pérez", "apodo": "ana.perez",
"email": "[email protected]", "email_verificado": true,
"nacimiento": "1990-05-12", "celular": "5491155551234",
"rol": { "codigo": "ONG", "etiqueta": "ONG" }, "avatar_url": null,
"organizaciones": [
{ "origen": "ONG", "id": 12, "nombre": "Asociación Civil Raíces", "rol": "AD",
"cannis_pro": { "habilitada": true, "plan": "gratis", "estado": "AC" } }
],
"afiliaciones": [ { "ong_id": 30, "nombre": "Cultivo Sur", "estado": "AC", "desde": "2024-11-02" } ],
"productos": { "cannis_os": true, "cannis_pro": true }
} },
"errores": null
}
Changes any subset of nombre, apellido, nacimiento and celular (null deletes the mobile number). Returns the full user. With /api/v1/usuario/contrasena the password is changed: actual, nueva and nueva_confirmation; if the current one is wrong, 422 with errores.actual.
Just the list of organizations from the full user: where the person is on the team, with their role and what they have of Cannis Pro. It is the list to choose from when requesting access.
errores, in Spanish.Retry-After.
What the API does not do today: recover the password or verify the email. Both are done from the website, with the same account. The formal v1 contract, in OpenAPI, is in the Cannis repository (docs/api/openapi.yaml).
09 Enter Cannis Pro
Cannis Pro is the cultivation module of Cannis. It uses the same account: a person who signs up on Cannis exists in Cannis Pro at once, with the same account number, and the other way round. What decides whether an organization can operate there is Cannis: it gives it a plan and, from that moment, its team gets in without another password. From the app, you get in with single-use access.
Requests access to Cannis Pro for one of the organizations the person operates. It returns a URL the app opens in the browser: Cannis Pro redeems it and signs in with that organization active. The access lasts two minutes and works only once.
ONG or ORGThe organization's origen, as it came in the user.id of that organization.{
"ok": true, "mensaje": "Listo.",
"datos": { "url": "https://app.cannis.pro/acceso-cannis?token=…", "vence_en": 120 },
"errores": null
}
No password travels in clear between the two systems, and every call is signed. An email that already has a Cannis account cannot be registered again on Cannis Pro: that person signs in with their Cannis password. That part of the API is only between Cannis and Cannis Pro and is not published.