Skip to content
Get an API key
Reference

Templates

10 endpoints in this family.

List message templates

GET/api/v1/templates

Returns the templates of the client you act on (the Nodium-Tenant header, or your default client). A template belongs to one client and is deployed to its WhatsApp accounts; status is where it stands at Meta: only approved can be sent. With scope=account (a key that sees every client), the templates of all your clients, each with its clientId and clientName.

A template is identified at Meta by its name AND its language — the same name in two languages is two templates. To send one, its name is enough: Nodium picks the approved version in the WhatsApp account of the number that sends.

Query parameters

  • scopestring

    account: the templates of every client of your account.

    account

Response

The templates, with where each stands at Meta.

  • data.templatesTemplate[]

    The templates, recently changed first.

  • data.summaryobject

    Counts: total, draft (never deployed), deployed.

Refusals

  • 403all_clients_required`scope=account` needs a key that sees every client.
curl https://nodium.io/api/v1/templates \ -H "Authorization: Bearer $NODIUM_KEY"

Response

200 OK
{ "data": { "templates": [ {} ], "summary": {} }}

Draft a template

POST/api/v1/templates

Writes a template for your account. Nothing is sent to Meta yet — drafting and deploying are two steps on purpose, because deploying reserves the name at Meta in each client's WhatsApp account. Deploy it with POST /templates/{id}/deploy.

The template belongs to the client you act on (the Nodium-Tenant header, or your default client) and is deployed to its WhatsApp accounts, including numbers it connects later.

Body

  • namestringrequired

    Lowercase letters, digits and underscores. Reserved at Meta on deployment.

  • languagestringrequired

    Language tag, for example fr or en_US.

  • categorystringrequired

    What Meta charges it as.

    UTILITYMARKETINGAUTHENTICATION

  • bodystringrequired

    The text, with {{1}} style placeholders. Not used for an AUTHENTICATION template: Meta writes its text.

  • authenticationobject

    For an AUTHENTICATION template only: { "securityRecommendation": true, "codeExpirationMinutes": 10, "buttonText": "Copy code" }. Meta writes the text (“*123456* is your verification code.”); the template gets one copy-code button. Send it with the code as variable 1.

  • headerstring

    An optional one-line text header.

  • headerMediaobject

    An image, video or document header instead of a text one: { "sampleId": "…", "filename": "menu.pdf" }, the sample uploaded with POST /templates/samples for the same client. Meta reviews the template with it; it is also the header sent when a message gives no other.

  • footerstring

    An optional footer, without placeholders.

  • examplesobject

    One sample value per numbered placeholder, by number: { "1": "Marie" }. Meta needs them to review a template that has placeholders; they are never used at send time.

  • buttonsobject[]

    Up to ten buttons: quick reply, url (two at most), phone number (one), copy code (one, marketing only), and one button that opens a WhatsApp Flow: { "type": "FLOW", "text": "Book", "flowId": "<Flow id>", "screen": "SLOTS" } — flowId from GET /flows, screen optional (the first screen by default; ignored for a dynamic Flow). The Flow must be published when the template is deployed; in each WhatsApp account of the client, Nodium uses the Flow of the same name.

Response

The template as drafted, not yet deployed.

  • data.templateTemplate
  • data.holesobject

    The placeholders to fill at send time: numbered and named.

Refusals

  • 409template_existsThis client already has a template with this name and language.
  • 422template_header_invalidThe `headerMedia` sample is unknown, the template has both a text header and a media header, or an `AUTHENTICATION` template has a header.
  • 422template_buttons_invalidA button is not allowed in this category (a copy-code button outside a marketing template), or the Flow button is invalid: unknown or deprecated Flow, more than one Flow button, label over 25 characters.
  • 422template_variables_invalidThe footer has a placeholder: Meta refuses it.
curl -X POST https://nodium.io/api/v1/templates \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "…", "language": "fr", "category": "UTILITY", "body": "…" }'

Response

200 OK
{ "data": { "template": { "id": "…", "name": "…", "language": "…", "category": "UTILITY", "status": "draft", "clientId": "…", "deployedAt": "…", "headerMedia": {}, "authentication": {}, "body": "…", "createdAt": "…", "updatedAt": "…" }, "holes": {} }}

Delete a template

DEL/api/v1/templates/{id}

Deletes a template of the client you act on that Meta holds nowhere: a draft, or one refused or never deposited in each of its WhatsApp accounts. A template in review or approved cannot be deleted: sends refer to it. Nothing is deleted at Meta.

Path parameters

  • idstring

    The template id.

Response

Confirmation that it is gone.

  • data.deletedboolean

    Always true.

Refusals

  • 409template_lockedMeta holds it (in review, approved, paused or disabled).
curl -X DELETE https://nodium.io/api/v1/templates/<id> \ -H "Authorization: Bearer $NODIUM_KEY"

Response

200 OK
{ "data": { "deleted": true }}

Get one template

GET/api/v1/templates/{id}

Returns a template of the client you act on (the Nodium-Tenant header, or your default client), its placeholders, and where it stands in each of that client's WhatsApp accounts: one line per account, including those it has not reached yet (not_deployed: a number is there but the template is not; no_number: the client has no number yet). A template of another client answers 404.

Path parameters

  • idstring

    The template id.

Response

The template, and its state in each WhatsApp account of its client.

  • data.templateTemplate
  • data.holesobject

    The placeholders to fill at send time: numbered and named.

  • data.deploymentsobject[]

    Its state in each WhatsApp account of its client.

    Show fields
    • wabaIdstring

      The client's WhatsApp Business account, or null.

    • statusstring

      Its state there.

      submittedapprovedrejectedpauseddisabledfailednot_deployedno_number

    • categorystring

      The category Meta kept there, when it differs from the template's; otherwise null.

    • rejectionReasonstring

      Why Meta refused it there, or why the deposit failed; otherwise null.

    • submittedAtstring

      When it was last sent to Meta there, or null.

    • reviewedAtstring

      When Meta last decided there, or null.

  • data.editableboolean

    Whether its text can change: not while Meta reviews it, nor once Meta disabled it. Approved or paused, it can be corrected (it then goes back to review).

  • data.categoryLockedboolean

    Whether its category is fixed: Meta holds it approved or paused.

curl https://nodium.io/api/v1/templates/<id> \ -H "Authorization: Bearer $NODIUM_KEY"

Response

200 OK
{ "data": { "template": { "id": "…", "name": "…", "language": "…", "category": "UTILITY", "status": "draft", "clientId": "…", "deployedAt": "…", "headerMedia": {}, "authentication": {}, "body": "…", "createdAt": "…", "updatedAt": "…" }, "holes": {}, "deployments": [ { "wabaId": "…", "status": "submitted", "category": "…", "rejectionReason": "…", "submittedAt": "…", "reviewedAt": "…" } ], "editable": true, "categoryLocked": true }}

Edit a template

PATCH/api/v1/templates/{id}

Changes a template of the client you act on, except while Meta reviews it or once Meta disabled it. After a rejection, fix it here and deploy again: the rejected copies go back to review. Approved or paused, it can also be corrected: the new text is sent to Meta first and the template goes back to review. Meta limits these corrections (one per 24 hours, ten per 30 days) and does not let the category change: its reason comes back as an error. Once deployed, the name and language are fixed — write a new template instead.

Path parameters

  • idstring

    The template id.

Body

  • namestring

    A new name, before its first deployment only.

  • languagestring

    A new language, before its first deployment only.

  • bodystring

    The text.

  • headerstring

    The text header line.

  • headerMediaobject

    An image, video or document header: { "sampleId": "…", "filename": "…" } from POST /templates/samples; null removes it. Kept as it is when omitted.

  • footerstring

    The footer, without placeholders.

  • authenticationobject

    For an AUTHENTICATION template: { securityRecommendation, codeExpirationMinutes, buttonText }. Kept as they are when omitted.

  • examplesobject

    One sample value per numbered placeholder, by number: { "1": "Marie" }. Kept as they are when omitted.

  • buttonsobject[]

    The buttons, as in POST /templates — including the one that opens a Flow: { "type": "FLOW", "text", "flowId", "screen" }.

  • categorystring

    What Meta charges it as.

    UTILITYMARKETINGAUTHENTICATION

Response

The template, updated, and the corrections sent to Meta.

  • data.templateTemplate
  • data.holesobject

    Its placeholders: numbered and named.

  • data.correctionsobject[]

    When it was approved or paused: one line per WhatsApp account, { clientId, clientName, wabaId, status, reason } — reason is set when Meta refused the correction there.

Refusals

  • 409template_lockedMeta is reviewing it, Meta disabled it, or its name or language changed after deployment.
  • 409template_category_lockedApproved or paused at Meta, its category cannot change.
  • 409template_edit_refusedMeta refused the correction everywhere (limit reached, text refused…); `data.corrections` says why. Nothing changed.
  • 409template_existsAnother template of this client already has this name and language.
  • 422template_header_invalidThe `headerMedia` sample is unknown, or the template would have both a text header and a media header.
curl -X PATCH https://nodium.io/api/v1/templates/<id> \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "…", "language": "…" }'

Response

200 OK
{ "data": { "template": { "id": "…", "name": "…", "language": "…", "category": "UTILITY", "status": "draft", "clientId": "…", "deployedAt": "…", "headerMedia": {}, "authentication": {}, "body": "…", "createdAt": "…", "updatedAt": "…" }, "holes": {}, "corrections": [ {} ] }}

Deploy a template to Meta

POST/api/v1/templates/{id}/deploy

External and final. Deposits a template of the client you act on in each WhatsApp account of its numbers, where Meta reviews it. Where it is missing it is deposited; where Meta rejected it, it goes back to review with its current text; where the deposit failed, it is retried; where it is in review or approved, nothing moves. Call it again at any time: it only does what is left to do.

Numbers the client connects later receive it automatically. Meta almost always answers submitted and reviews later: follow template.status_changed, or POST /templates/{id}/refresh.

Path parameters

  • idstring

    The template id.

Response

The template and its state in each WhatsApp account.

  • data.templateTemplate
  • data.resultsobject[]

    One line per WhatsApp account of the client.

    Show fields
    • clientIdstring

      The client.

    • clientNamestring

      Its name.

    • wabaIdstring

      Its WhatsApp Business account, or null when it has no number yet.

    • statusstring

      Where the template stands there now.

      submittedapprovedrejectedpauseddisabledfailedno_number

    • reasonstring

      Why the deposit failed, or null.

Refusals

  • 503meta_token_missingThe WhatsApp connection is not configured on this instance.
curl -X POST https://nodium.io/api/v1/templates/<id>/deploy \ -H "Authorization: Bearer $NODIUM_KEY"

Response

200 OK
{ "data": { "template": { "id": "…", "name": "…", "language": "…", "category": "UTILITY", "status": "draft", "clientId": "…", "deployedAt": "…", "headerMedia": {}, "authentication": {}, "body": "…", "createdAt": "…", "updatedAt": "…" }, "results": [ { "clientId": "…", "clientName": "…", "wabaId": "…", "status": "submitted", "reason": "…" } ] }}

Ask Meta where a template stands

POST/api/v1/templates/{id}/refresh

Re-reads at Meta the state of the template in each WhatsApp account of its client and stores it. Returns changed: 0 when nothing moved, so polling is cheap. Every change is also sent as template.status_changed, so you rarely need to poll.

Path parameters

  • idstring

    The template id.

Response

The template and how many WhatsApp accounts changed.

  • data.templateTemplate
  • data.changedinteger

    How many WhatsApp accounts changed state.

Refusals

  • 503meta_token_missingThe WhatsApp connection is not configured on this instance.
curl -X POST https://nodium.io/api/v1/templates/<id>/refresh \ -H "Authorization: Bearer $NODIUM_KEY"

Response

200 OK
{ "data": { "template": { "id": "…", "name": "…", "language": "…", "category": "UTILITY", "status": "draft", "clientId": "…", "deployedAt": "…", "headerMedia": {}, "authentication": {}, "body": "…", "createdAt": "…", "updatedAt": "…" }, "changed": 0 }}

Import the templates already at Meta

POST/api/v1/templates/import

Copies into Nodium the templates found in the WhatsApp Business accounts of the client you act on (the Nodium-Tenant header, or your default client). With scope=account (a key that sees every client), those of all your clients. Reads at Meta, writes on the Nodium side only. Each template found becomes a template of the client where it was found; one with the same name and language there is recorded as deployed instead.

Query parameters

  • scopestring

    account: every client of your account.

    account

Response

How many templates were read, and what could not be.

  • data.countinteger

    How many templates were read.

  • data.skippedobject[]

    What was left aside, and why.

Refusals

  • 409no_wabaNo WhatsApp number is connected here yet.
  • 403all_clients_required`scope=account` needs a key that sees every client.
curl -X POST https://nodium.io/api/v1/templates/import \ -H "Authorization: Bearer $NODIUM_KEY"

Response

200 OK
{ "data": { "count": 0, "skipped": [ {} ] }}

Prepare a header sample upload

POST/api/v1/templates/samples

Starts the upload of the sample of an image, video or document header. Send what the file is; you get a sampleId and an uploadUrl. Upload the file there yourself with PUT (the raw bytes, with its Content-Type), then confirm it with POST /templates/samples/{id}/confirm. Cite the sampleId as headerMedia.sampleId when you write the template (POST /templates or PATCH /templates/{id}) for the same client.

Meta reviews the template with this sample. It is also the header sent with each message, unless the message gives its own (headerMediaId on POST /messages). Accepted: JPEG or PNG images up to 5 MB, MP4 videos up to 16 MB, PDF documents up to 100 MB. The upload URL expires after two hours. Nothing is sent to Meta by this call.

Body

  • filenamestringrequired

    The file name, as shown with a document header.

  • mimeTypestringrequired

    image/jpeg, image/png, video/mp4 or application/pdf.

  • sizeintegerrequired

    Its size in bytes.

Response

The `sampleId` and the URL to upload the file to.

  • data.sampleIdstring

    Confirm it, then pass it as headerMedia.sampleId.

  • data.kindstring

    The header it makes.

    imagevideodocument

  • data.uploadUrlstring

    Upload the file here with PUT, its bytes as the body and its Content-Type.

Refusals

  • 413file_too_largeOver 5 MB for an image, 16 MB for a video, 100 MB for a document.
  • 415file_type_refusedNot a JPEG or PNG image, an MP4 video or a PDF document.
  • 402storage_quotaThe media storage included in your plan is full and your plan does not bill extra gigabytes.
  • 429rate_limitedMore than 30 uploads in a minute for this key and client.
curl -X POST https://nodium.io/api/v1/templates/samples \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "filename": "…", "mimeType": "…", "size": 0 }'

Response

200 OK
{ "data": { "sampleId": "…", "kind": "image", "uploadUrl": "…" }}

Confirm a header sample

POST/api/v1/templates/samples/{id}/confirm

Confirms the header sample you uploaded to the uploadUrl of POST /templates/samples. Nodium reads the stored file itself — its real size and type — and counts it against your media storage. Then cite the sampleId as headerMedia.sampleId; an unconfirmed sample is refused there and removed after a day. A file over the limits is removed and refused. Confirming again returns the same sample and counts it once.

Path parameters

  • idstring

    The sampleId.

Body

  • filenamestring

    The file name, as shown with a document header.

Response

The sample, ready to cite in a template.

  • data.sampleIdstring

    Pass it as headerMedia.sampleId when writing the template.

  • data.kindstring

    The header it makes.

    imagevideodocument

  • data.mimeTypestring

    Its media type, as stored.

  • data.filenamestring

    Its name.

  • data.sizeinteger

    Its size in bytes, as stored.

Refusals

  • 404not_foundNothing was uploaded under this id for this client.
  • 413file_too_largeOver 5 MB for an image, 16 MB for a video, 100 MB for a document.
  • 415file_type_refusedNot a JPEG or PNG image, an MP4 video or a PDF document.
  • 402storage_quotaThe media storage included in your plan is full and your plan does not bill extra gigabytes.
curl -X POST https://nodium.io/api/v1/templates/samples/<id>/confirm \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "filename": "…" }'

Response

200 OK
{ "data": { "sampleId": "…", "kind": "image", "mimeType": "…", "filename": "…", "size": 0 }}