Skip to content
Public API

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.
How a request travels
1 The person fills in your site's form and the reCAPTCHA
2 Your server sends it to Cannis with the token
3 Cannis validates the data and the reCAPTCHA
4 The request enters Cannis OS and the email notice goes out

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.

BaseHTTPS
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.

HeaderBearer
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

GET /api/provincias No token

Returns the Argentine provinces with the identifier the membership request expects later. No parameters. The response is an array.

Response · 200
JSONArray
[
  { "id_provincia": 1, "nombre": "Buenos Aires" },
  { "id_provincia": 2, "nombre": "Catamarca" }
]
Example
curlbash
curl -H "Accept: application/json" https://ns1.cannis.org/api/provincias

05 Localities

GET /api/localidades No token

The localities of a province: the classic second dropdown. The response is an array.

Query parameters
id_provincia* integer The id_provincia returned by the provinces lookup. It must exist.
Response · 200
JSONArray
[
  { "id": 1042, "nombre": "Rosario" },
  { "id": 1043, "nombre": "Venado Tuerto" }
]
Response · 422

If id_provincia is missing or does not exist, the errors come per field:

JSON422
{
  "message": "Parámetros inválidos",
  "errors": {
    "id_provincia": [ "El campo id provincia es obligatorio." ]
  }
}
Example
curlbash
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.

POST /api/post/solicitud_paciente With token

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.

Identity and contact
nombre* text · 100 The person's first name.
apellido* text · 100 Last name.
documento* 8 to 9 digits Digits only. Unique per organization: if that person already sent a request, the second one is rejected.
cuit 11 digits The person’s CUIT/CUIL (Argentine tax ID), with or without dashes. It is checked to have 11 digits. Ask for it in your form: it will soon be required.
numero_tramite text · 20 ID card (DNI) procedure number. Optional.
fecha_nacimiento* date Format YYYY-MM-DD.
email* email · 255 Unique per organization.
celular* text · 20 Unique per organization.
profesion text · 100 Optional.
Address
provincia* integer The id_provincia from the provinces lookup.
localidad* integer The id from the localities lookup.
calle* text · 255 Street.
numero* text · 10 Street number.
REPROCANN and consent
reprocann* boolean Whether the person already has a REPROCANN.
estado_reprocann text · 5 The status of the application, if they know it.
consentimiento* must equal 1 Without informed consent there is no request. It is not a formality: it is the legal requirement.
captcha* text The result of the reCAPTCHA the person solved in your form.
Files and free text
carnet_reprocann PDF · 5 MB The REPROCANN card. Optional.
declaracion_jurada PDF · 5 MB Sworn statement. Optional.
consentimiento_bilateral PDF · 5 MB Consent signed by both parties. Optional.
descripcion text Whatever the person wants to tell about their case.
informacion_adicional text A free field for whatever your form needs to store.
The shape of the request
curlIllustrative example
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]"
Response · 200
JSONObject
{
  "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.

JSON422 · data
{
  "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:

JSON422 · reCAPTCHA
{
  "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:

JSON401
{
  "status": "error",
  "message": "Token API no proporcionado."
}
401 No token The Authorization header is missing or does not start with Bearer.
403 Invalid token It was revoked, it expired or it has no associated organization.
422 Validation In the submission, fields are missing or wrong, there is a duplicate or the reCAPTCHA did not verify. In localities, the province is missing or does not exist.
429 Too many requests The per-minute limit of the open lookups was exceeded. With 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.

BaseHTTPS · JSON
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.

JSONThe envelope
{ "ok": true,  "mensaje": "Listo.", "datos": { "…": "…" }, "errores": null }
{ "ok": false, "mensaje": "Qué pasó, en claro", "datos": null, "errores": { "campo": [ "mensaje" ] } }
The application key

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.

The user token

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»).

HeaderBearer
X-Cannis-App: <clave-de-aplicación>
Authorization: Bearer <token-de-usuario>
Accept: application/json
POST /api/v1/registro No token

Creates the account and returns the first token. Up to five attempts per minute from the same IP.

Body
nombre*text · 100First name.
apellido*text · 100Last name.
email*emailIt cannot already exist in Cannis.
password*text · 8 or moreWith letters and numbers. Goes with password_confirmation.
nacimiento*dateYYYY-MM-DD, before today.
celulartext · 15Optional.
acepta_terminos*booleanMust be true: the terms and the privacy policy.
dispositivotext · 80The device name, for the list of sessions.
Response · 201
JSONObject
{
  "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.

POST /api/v1/ingresar No token

Email and password, and it returns a token with the same shape as sign-up. Wrong credentials: 422 with errores.email.

curlbash
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"}'
GET /api/v1/usuario With token

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.

Response · 200
JSONObject
{
  "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
}
PUT /api/v1/usuario With token

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.

GET /api/v1/usuario/organizaciones With token

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.

Sessions
POST /api/v1/salir With token Revokes the token it was requested with. It is the sign-out of that device.
GET /api/v1/sesiones With token The account's live sessions: each device, when it was last used and when it expires.
DELETE /api/v1/sesiones/{id} With token Closes another session of the same account.
v1 errors
401No key or no tokenThe application key is missing or was revoked; or the token is missing, expired or was revoked. The app goes back to the sign-in screen.
422ValidationPer-field messages come in errores, in Spanish.
429Too many requestsFive per minute on sign-up and sign-in, 120 on the rest, per IP; with 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.

POST /api/v1/usuario/cannis-pro/acceso With token

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.

Body
origen*ONG or ORGThe organization's origen, as it came in the user.
id*integerThe id of that organization.
Response · 200
JSONObject
{
  "ok": true, "mensaje": "Listo.",
  "datos": { "url": "https://app.cannis.pro/acceso-cannis?token=…", "vence_en": 120 },
  "errores": null
}
403Not an operatorThe person is not on that organization's team.
409No planThe organization has no Cannis Pro plan: it asks Cannis for one.
502 · 503Cannis Pro did not respondOr the integration is turned off. Try again.
How Cannis and Cannis Pro connect
1 Every Cannis account and organization is copied to Cannis Pro, with the same number
2 Cannis gives the organization its plan; without a plan, Pro knows it but does not let it operate
3 Its team gets in from Cannis OS or from the app, without another password
4 Whoever signs up on Cannis Pro also exists on Cannis, with the same account

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.