Rioko

Version 1 · updated on 18 September 2026

Rioko partner API

Everything a partner's engineering team needs to connect its platform to Rioko: invite clients, follow their state, and keep both lists in step.

Overview

The API lets a partner's platform invite its clients to Rioko and know, at any moment, where each one stands: whether they signed up, whether the partner confirmed them, whether they granted access, which integrations they have and when their last document was issued.

It is server to server. It does not replace the partner portal: confirming or rejecting a client who came through the link, creating managed accounts, configuring integrations and paying subscriptions still happen in the portal, by a signed-in person.

  • It never writes to a client's integrations.
  • It never issues, finalises or cancels tax documents.
  • It never confirms or rejects pending clients: a leaked key cannot decide who the partner's clients are.
  • Each key sees only the clients of the partner it belongs to.

Getting started: keys

Keys are created in the partner portal, API tab, by the owner or by a team member with admin permission. A read-only member sees the list but cannot create or revoke.

A key looks like rkp_live_ followed by 40 hexadecimal characters and is shown exactly once, when it is created. Rioko stores only a hash: if a key is lost, revoke it and create another.

  • Keep the key on your platform's server (an environment variable or a secrets vault). Never in a browser, a mobile app or a repository.
  • A partner can have at most 2 active keys. To rotate without downtime: create the new one, switch the platform to it, then revoke the old one.
  • Every key expires. By default after 365 days, which is also the maximum.
  • A revoked key stops working on the next call.
  • If the person who created a key leaves the partner's team on Rioko, the keys they created are revoked automatically.

Basics

TopicRule
Base URLhttps://rioko.online/api/partner/v1
FormatJSON in UTF-8. Requests with a body send Content-Type: application/json.
DatesISO 8601 in UTC, for example 2026-09-17T10:00:00.000Z.
VersioningWithin v1 only compatible changes ship: new response fields, new endpoints. Your code should ignore fields it does not know. A breaking change ships as v2, announced in advance.
Paginationlimit (1 to 100, default 50) and cursor. Pass the previous response's next_cursor until it is null. On /clients the cursor is opaque; on /invites it is an offset.
PollingThere are no webhooks in v1. Call /clients with updated_since set to the highest updated_at you have seen, at most every 15 minutes.
IdempotencyFor invites, by external_ref: repeating a request neither creates a second invite nor sends another email.
Limits50 invites per request; 200 invite emails per partner per rolling 24 hours, portal and API together; at most 2 emails to the same address in 24 hours. Too many requests per minute are refused with HTTP 429.
CORSNone. The API does not answer browser requests.

Authentication

Every call carries the key in the Authorization header. Without it the request is refused before it reaches the API.

An unknown, expired or revoked key gets exactly the same 401, on purpose. A suspended partner, or one that has not accepted the current Partner Terms, gets 403.

curl https://rioko.online/api/partner/v1/me \
  -H "Authorization: Bearer rkp_live_3f9c…"
HTTPcodeWhen
401missing_keyNo Authorization header, or no Bearer rkp_ prefix.
401invalid_keyMalformed, unknown, expired or revoked key.
403partner_inactiveThe partner is suspended, ended, or has not accepted the current Partner Terms.

Endpoints

GET/api/partner/v1/me

The key's partner, its limits and the key itself.

Useful to check the connection and how many invites still fit in the last 24 hours.

Example

curl https://rioko.online/api/partner/v1/me \
  -H "Authorization: Bearer rkp_live_3f9c…"

Response · 200

{
  "partner": { "id": "exemplo", "name": "Exemplo Lda", "status": "active" },
  "landing_url": "https://rioko.online/pt/p/exemplo",
  "limits": {
    "invites_per_request": 50,
    "invites_per_24h": 200,
    "invites_left_24h": 187,
    "page_max": 100
  },
  "key": { "label": "Plataforma produção", "expires_at": "2027-09-17T10:00:00.000Z" }
}

Possible errors

missing_keyinvalid_keypartner_inactivemethod_not_allowed

POST/api/partner/v1/invites

Invite clients by email.

Each invite is an email with a unique link (valid for 30 days, single use) to the partner's page on Rioko, where the client signs up or signs in, accepts the terms and chooses whether to give the partner access.

If the client accepts from a verified email matching the invite, they are confirmed at once. If they accept from another email, they are pending and the partner confirms them in the portal.

With send_email set to false Rioko sends no email: the response carries invite_url and your platform delivers it. The invite still counts toward the limits.

invite_url is returned only in the response that creates the invite. A repeated request with the same external_ref and email answers exists, with no link and no new email.

An address that is already another partner's client on Rioko answers sent like any other, but receives no email: no API response reveals who already uses Rioko.

Parameters

FieldTypeRequiredRules
invitesarrayyes1 to 50 items.
invites[].emailstringyesAn email address. Stored lowercased.
invites[].external_refstringnoThe client's id in your platform, up to 128 characters. Unique per partner among links that have not ended.
send_emailbooleannoDefaults to true.

Example

curl -X POST https://rioko.online/api/partner/v1/invites \
  -H "Authorization: Bearer rkp_live_3f9c…" \
  -H "Content-Type: application/json" \
  -d @invites.json

Request

{
  "invites": [
    { "email": "[email protected]", "external_ref": "acad_1042" },
    { "email": "[email protected]", "external_ref": "acad_1043" }
  ],
  "send_email": true
}

Response · 201 when at least one invite was created, 200 otherwise

{
  "results": [
    {
      "email": "[email protected]",
      "external_ref": "acad_1042",
      "status": "sent",
      "id": "6f1c2d0e-8a4b-4f3e-9b8e-2c1d5e7a9b10",
      "invite_url": "https://rioko.online/pt/p/exemplo/exemplo-3fa91c0e5b7d2a8f4c6e1b09",
      "expires_at": "2026-10-17T10:00:00.000Z"
    },
    {
      "email": "[email protected]",
      "external_ref": "acad_1043",
      "status": "exists",
      "id": "9b2e7a4c-1d3f-4e5a-8b6c-0d9e2f1a3b4c"
    }
  ],
  "invites_left_24h": 186
}

Possible errors

invalid_requesttoo_many_invitesmissing_keyinvalid_keypartner_inactive

GET/api/partner/v1/invites

The invites sent, newest first.

Never returns tokens or links. status is open, expired, accepted or revoked.

Parameters

ParameterTypeRequiredRules
statusstringnoopen, expired, accepted or revoked.
limitintegerno1 to 100, default 50.
cursorstringnoThe previous page's next_cursor.

Example

curl "https://rioko.online/api/partner/v1/invites?status=open&limit=50" \
  -H "Authorization: Bearer rkp_live_3f9c…"

Response · 200

{
  "data": [
    {
      "id": "6f1c2d0e-8a4b-4f3e-9b8e-2c1d5e7a9b10",
      "external_ref": "acad_1042",
      "email": "[email protected]",
      "status": "open",
      "sent_at": "2026-09-17T10:00:00.000Z",
      "expires_at": "2026-10-17T10:00:00.000Z",
      "accepted_at": null,
      "resends": 0
    }
  ],
  "next_cursor": null
}

Possible errors

invalid_cursormissing_keyinvalid_keypartner_inactive

DELETE/api/partner/v1/invites/{id}

Revoke an open invite.

The link in the email stops working. Only the partner's own invites that are still open can be revoked.

Example

curl -X DELETE https://rioko.online/api/partner/v1/invites/6f1c2d0e-8a4b-4f3e-9b8e-2c1d5e7a9b10 \
  -H "Authorization: Bearer rkp_live_3f9c…"

Response · 200

{ "id": "6f1c2d0e-8a4b-4f3e-9b8e-2c1d5e7a9b10", "status": "revoked" }

Possible errors

not_foundmissing_keyinvalid_keypartner_inactive

GET/api/partner/v1/clients

The partner's clients and where each one stands.

Ordered by updated_at, then id, oldest first, which keeps polling with updated_since stable.

updated_at changes with anything that happens to the link: sign-up, confirmation or rejection, access granted or withdrawn, a managed account handed over, the link ending.

What each client carries depends on where it stands (see States and consent): a pending client shows only company name, VAT number, email and date; without access, only name, code and mode; with access, also its integrations and last document; an ended link, only the end.

state, for clients with access, is live, connecting, signed_up, needs_attention or subscription_inactive.

On each integration, subscription says how the client's Rioko subscription for it stands: active (paid), trial (in a trial period), inactive (ended or suspended: that integration does not invoice) or none (never subscribed). subscription_ending is true when it will not renew.

Parameters

ParameterTypeRequiredRules
updated_sinceISO 8601 datenoOnly clients whose updated_at is strictly later.
statusstringnoactive or ended.
validationstringnopending or confirmed.
limitintegerno1 to 100, default 50.
cursorstringnoThe previous page's next_cursor, as received.

Example

curl "https://rioko.online/api/partner/v1/clients?updated_since=2026-09-18T00:00:00.000Z" \
  -H "Authorization: Bearer rkp_live_3f9c…"

Response · 200

{
  "data": [
    {
      "id": "0a7d…",
      "external_ref": "acad_1042",
      "updated_at": "2026-09-18T09:12:44.000Z",
      "status": "active",
      "validation": "pending",
      "company_name": "Academia Norte, Lda",
      "nif": "516000000",
      "email": "[email protected]",
      "claimed_at": "2026-09-18T09:12:44.000Z"
    },
    {
      "id": "4c1e…",
      "external_ref": "acad_0981",
      "updated_at": "2026-09-19T15:02:10.000Z",
      "status": "active",
      "validation": "confirmed",
      "client_code": "RIO-7K2M9Q",
      "name": "Estúdio Sul",
      "mode": "referred",
      "since": "2026-09-02T11:40:00.000Z",
      "access": false
    },
    {
      "id": "8e33…",
      "external_ref": "acad_0770",
      "updated_at": "2026-09-20T08:30:00.000Z",
      "status": "active",
      "validation": "confirmed",
      "client_code": "RIO-3H8TQ1",
      "name": "Clube Oeste",
      "mode": "referred",
      "since": "2026-08-21T10:00:00.000Z",
      "access": true,
      "state": "live",
      "connections": [
        { "source": "stripe", "destination": "moloni", "status": "active", "subscription": "active", "subscription_ending": false }
      ],
      "last_document_at": "2026-09-20T08:29:51.000Z"
    },
    {
      "id": "b51f…",
      "external_ref": "acad_0655",
      "updated_at": "2026-09-21T17:45:00.000Z",
      "status": "ended",
      "end_reason": "client_left",
      "ended_at": "2026-09-21T17:45:00.000Z"
    }
  ],
  "next_cursor": "MjAyNi0wOS0yMVQxNzo0NTowMC4wMDBafGI1MWY"
}

Possible errors

invalid_cursormissing_keyinvalid_keypartner_inactive

GET/api/partner/v1/clients/{client_code}

One client, by its Rioko code (RIO-…).

Only confirmed clients have a visible code. Another partner's code answers 404, like one that does not exist.

Example

curl https://rioko.online/api/partner/v1/clients/RIO-3H8TQ1 \
  -H "Authorization: Bearer rkp_live_3f9c…"

Response · 200: the same object as in /clients

{
  "id": "8e33…",
  "client_code": "RIO-3H8TQ1",
  "access": true,
  "state": "live"
}

Possible errors

not_foundmissing_keyinvalid_keypartner_inactive

GET/api/partner/v1/clients/{client_code}/documents

What happened to a client's documents after they were issued.

So the platform can show the merchant what changed without asking the invoicing software.

It does NOT carry the document number or its link: those live in the invoicing software, and were already returned in the response to the request that issued each document. This is what changed SINCE: a draft closed by hand, a receipt, a credit note.

`state` is the furthest point the document reached: `issued`, `held`, `finalized`, `settled` or `credited`.

`updated_since` returns only what moved since that date, which is how you poll without re-reading everything. The event history is pruned at 90 days (365 for the ones that are evidence), so an old document lists with less of its story.

Needs access granted by the client. Without it, 404, like a code that does not exist.

Example

curl "https://rioko.online/api/partner/v1/clients/RIO-3H8TQ1/documents?updated_since=2026-09-01T00:00:00Z&limit=50"   -H "Authorization: Bearer rkp_live_3f9c…"

Response · 200: one page of documents, oldest first

{
  "client_code": "RIO-3H8TQ1",
  "external_ref": "acad_42",
  "data": [
    {
      "external_id": "mf_acad42_1042",
      "document_id": "1025042934",
      "state": "settled",
      "held": null,
      "issued_at": "2026-09-24T09:12:00Z",
      "finalized_at": "2026-09-24T09:12:03Z",
      "settled_at": "2026-09-30T10:04:00Z",
      "credited_at": null,
      "updated_at": "2026-09-30T10:04:00Z"
    }
  ],
  "next_cursor": null
}

Possible errors

not_foundinvalid_cursormissing_keyinvalid_keypartner_inactive

States and consent

An invite or the partner's public link gives the client the partner price and attributes them to the partner. It never gives access to the client's data: access is the client's own explicit choice, which they can withdraw at any time.

  • end_reason: client_left (the client left), partner_dropped (the partner dropped the client), rejected (the partner did not recognise the client), account_deleted, admin.
  • The external_ref of a link the partner rejected or dropped cannot be reused to invite the same client: the invite answers external_ref_closed.
Situationstatus / validationWhat the API shows
Invite sent, no account yetin /invites: openEmail, dates, number of resends.
Invite expired or revokedin /invites: expired or revokedThe same.
Client signed up, awaiting the partner's confirmationactive / pendingCompany name, VAT number, email, date.
Confirmed, no accessactive / confirmed, access falseName, RIO- code, mode, since when.
Confirmed, with accessactive / confirmed, access trueAlso state, integrations and last document.
Link endedendedOnly end_reason and ended_at.

The outcome of each invite

statusMeaning
sentInvite created and email sent (or delivered by your platform, with send_email false).
invalidNot an email address.
opted_outThis address asked not to receive this partner's invites.
already_invitedThis partner already has an open invite for this address.
too_soonThis address already received 2 emails from this partner in the last 24 hours.
daily_limitThe partner reached 200 invite emails in the last 24 hours.
failedThe email provider refused the message. Nothing was kept: you may try again.
unconfirmedDelivery was not confirmed. The invite exists; if it does not arrive, resend it from the portal.
existsThe same external_ref and email already have an invite or client. Nothing was created or sent.
external_ref_conflictThat external_ref already belongs to another address for this partner.
external_ref_closedThat external_ref belongs to a link the partner rejected or dropped.

Error reference

Every error has the same shape. Handle it by code, not by message.

{ "error": { "code": "invalid_key", "message": "The key is unknown, expired or revoked." } }
HTTPcodeMeaning
400invalid_requestMissing body, invalid JSON or missing fields.
400too_many_invitesMore than 50 invites in one request.
400invalid_cursorThe cursor is not valid.
401missing_keyNo key.
401invalid_keyUnknown, expired or revoked key.
403partner_inactivePartner suspended, ended or with the Terms not accepted.
404not_foundThe resource does not exist or is not this partner's.
405method_not_allowedMethod not supported on this path.
429(no guaranteed body)Too many requests per minute. Wait and retry.
503unavailableBriefly unavailable. Retry with increasing backoff.

Data the API never returns

The partner is responsible for the data it receives, under the partner agreement and the data processing agreement it signed with Kapta.

  • Integration credentials (API keys, OAuth tokens, webhook secrets).
  • The clients' buyers' data and invoice contents.
  • Tax document numbers.
  • Stripe ids (customers, subscriptions, payments).
  • Emails or names of the partner's team.
  • Invite tokens, except invite_url in the response that creates the invite.

Security checklist

  • The key lives only on the server, out of source code and logs.
  • Use one key per environment (production and testing).
  • Rotate keys before they expire, using the second key to avoid downtime.
  • Revoke at once a key that may have been exposed.
  • Do not write invite_url to shared logs: whoever holds the link can accept the invite.
  • Poll with updated_since and keep to 15 minutes; handle 429 and 503 with increasing backoff.

Stripe data contract

For academies that take payments in Stripe, the invoice comes out right only if the payment carries the payer's details. Kapta sends the full document; in short:

  • The payer's identity on the Customer: name, email, address and tax_ids (eu_vat, PT followed by the VAT number). Subscription renewals read the Customer.
  • Metadata repeated where Stripe does not copy it: the Checkout Session metadata, payment_intent_data.metadata and subscription_data.metadata. The VAT number goes in the nif key.
  • No other key may contain nif, vat, tax, tin, iva, ust, cif or fiscal in its name, and no value or description may hold 9 or more consecutive digits other than the VAT number.
  • A stable payment_intent_data.description per product (no student name, no month); Products with stable names for subscriptions.
  • Payments on the academy's own Stripe account (direct charges). Rioko never asks for or accepts the platform's key.

Test environment

Ask Kapta for a test partner (for example exemplo-teste) and create test keys in that partner's portal. Test invites and clients are real records on Rioko: use email addresses you control and tell Kapta when you are done, so the test partner can be ended.

GET /me, GET /invites and GET /clients are safe at any time. POST /invites with send_email false sends no email, but counts toward the limits.

Changelog

DateVersionChanges
17 Sep 2026v1First release: /me, /invites, /clients.

Support

Technical questions, a compromised key or a test partner: [email protected]. Include the partner id and, if any, the invite or client id. Never send the key.