Developer API

Developer API

Build against the TEMS RECO marketplace.

This documents the subset of our API meant for external use — public marketplace browsing, and, for partner representatives, managing your own product catalogue. It does not cover internal-only endpoints (account management, device telemetry, payments, admin tooling).

Overview

Base URL

All paths below are relative to your environment's API base URL — confirm the production value with your TEMS RECO contact.

https://api.temsreco.com/api

Headers

Send on every request:

x-api-key: YOUR_API_KEY
Content-Type: application/json

Add this once you've authenticated (see below):

Authorization: Bearer YOUR_ACCESS_TOKEN

Authentication

Public endpoints (marketplace browsing) need no authentication. Everything under Your catalogue and Bulk import requires a partner account — call POST /auth/login with the credentials a TEMS RECO admin provisioned for you, and pass the returned accessToken as a bearer token. Access tokens expire after 30 minutes; use POST /auth/refresh-token to get a new one, or simply log in again.

A partner account only ever sees and modifies products under its own partner — this is enforced server-side, not just in the UI.

Response format

Every JSON response follows the same envelope:

{ "success": true, "message": "...", "data": { ... } }
{ "success": false, "error": "What went wrong" }

Rate limits

100 requests per 15 minutes per IP on general endpoints. Authentication endpoints have their own stricter limits (e.g. login is 5 attempts per 15 minutes). A rate-limited request returns HTTP 429.

Authentication

Every endpoint below acts on your own account. There is no separate "API key for server-to-server access" — a partner rep authenticates the same way a person would through the login form.

POST/auth/signupPublic

Register a new account. Partner rep accounts are provisioned by TEMS RECO admins, not via self-signup — this endpoint is for the general end-customer account type.

ParameterInTypeDescription
fullName *bodystring2–100 characters
email *bodystringMust be a valid, unique email
password *bodystringMinimum 6 characters
phone *bodystring
address *bodystring
lga *bodystring
state *bodystring
accountType *body"home" | "business"

Request

curl -X POST https://api.temsreco.com/api/auth/signup \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "fullName": "Ada Lovelace",
    "email": "ada@example.com",
    "password": "a-strong-password",
    "phone": "+2348000000000",
    "address": "12 Providence Street",
    "lga": "Eti-Osa",
    "state": "Lagos",
    "accountType": "business"
  }'

Response

{
  "success": true,
  "message": "Account created",
  "data": {
    "user": { "_id": "...", "fullName": "Ada Lovelace", "email": "ada@example.com", "role": "user", "...": "..." },
    "accessToken": "eyJhbGciOi..."
  }
}
POST/auth/loginPublic

Log in. This is how a partner rep account authenticates — use the credentials a TEMS RECO admin provisioned for you.

ParameterInTypeDescription
email *bodystring
password *bodystring

Request

curl -X POST https://api.temsreco.com/api/auth/login \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{ "email": "reps@ja-solar.example", "password": "your-password" }'

Response

{
  "success": true,
  "message": "Login successful",
  "data": {
    "user": { "_id": "...", "role": "partner", "partnerId": "...", "...": "..." },
    "accessToken": "eyJhbGciOi..."
  }
}
POST/auth/refresh-tokenPublic

Access tokens expire (30 minutes by default). This exchanges the refresh token — set as an httpOnly cookie by /login — for a new access token. A browser client gets this for free; a server-side integration needs to persist cookies across requests (e.g. an axios instance with a cookie jar) or simply re-call /login when a request 401s.

Response

{
  "success": true,
  "data": { "accessToken": "eyJhbGciOi..." }
}
POST/auth/logoutRequires partner account

Invalidate the current refresh token.

Response

{ "success": true, "message": "Logged out" }

Marketplace (public)

No authentication required. This is the same data the public TEMS RECO marketplace pages (temsreco.com/store) are built from — published products only.

GET/productsPublic

Browse published products across every partner.

ParameterInTypeDescription
categoryquerystringFilter by exact category match
partnerIdquerystringFilter to one partner

Request

curl https://api.temsreco.com/api/products?category=Solar%20%26%20Storage \
  -H "x-api-key: YOUR_API_KEY"

Response

{
  "success": true,
  "data": {
    "products": [
      {
        "_id": "...",
        "partnerId": { "_id": "...", "name": "JA Solar", "slug": "ja-solar", "logoUrl": "" },
        "manufacturer": "JA Solar",
        "name": "JAM66D46 LB",
        "category": "Solar & Storage",
        "retailPrice": 490000,
        "currency": "NGN",
        "bnplEligible": false,
        "stockStatus": "lead_time",
        "isPublished": true,
        "specs": { "Power range": "705W - 730W", "...": "..." }
      }
    ]
  }
}
GET/products/{id}Public

Get a single published product by id.

Response

{ "success": true, "data": { "product": { "_id": "...", "name": "...", "...": "..." } } }
GET/partners/activePublic

List active hardware partners — the same data behind the "Hardware partners" strip on the marketplace.

Response

{
  "success": true,
  "data": {
    "partners": [
      { "name": "JA Solar", "slug": "ja-solar", "logoUrl": "", "description": "Global PV module manufacturer..." }
    ]
  }
}

Your catalogue

Requires a partner account (Authorization: Bearer <accessToken> from /auth/login). A partner rep only ever sees and modifies their own partner's products — any partnerId sent in a request body is ignored server-side and forced to your own.

GET/products/mine/listRequires partner account

List every product under your partner — published and unpublished.

Response

{ "success": true, "data": { "products": [ { "_id": "...", "isPublished": false, "...": "..." } ] } }
POST/productsRequires partner account

Create a product under your catalogue.

ParameterInTypeDescription
name *bodystring
category *bodystringFree text, not a fixed enum
manufacturerbodystringDisplay brand, e.g. "Dyness"
descriptionbodystring
retailPricebodynumber | nullOmit or send null to show "Enquire for pricing"
bnplEligiblebodybooleanForced to false server-side if retailPrice is null
warrantybodystring
certificationsbodystring[]
skubodystring
stockStatusbody"in_stock" | "lead_time" | "made_to_order" | "unavailable"
isPublishedbodybooleanDefaults to true on this endpoint (unlike bulk import, which defaults to false)
specsbodyobject (string values)Free-form key/value spec sheet, e.g. { "Nominal energy": "5.12 kWh" }

Request

curl -X POST https://api.temsreco.com/api/products \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -d '{
    "name": "DL5.0C",
    "category": "Solar & Storage",
    "manufacturer": "Dyness",
    "retailPrice": 1150000,
    "bnplEligible": true,
    "stockStatus": "lead_time",
    "specs": { "Nominal energy": "5.12 kWh", "Battery type": "LiFePO4" }
  }'

Response

{ "success": true, "message": "Product created", "data": { "product": { "_id": "...", "...": "..." } } }
PATCH/products/{id}Requires partner account

Update one of your products. 404s if the product doesn't belong to your partner.

Response

{ "success": true, "message": "Product updated", "data": { "product": { "...": "..." } } }
DELETE/products/{id}Requires partner account

Delete one of your products. 404s if the product doesn't belong to your partner.

Response

{ "success": true, "message": "Product deleted", "data": {} }
POST/products/{id}/imagesRequires partner account

Attach an image to a product. Send as multipart/form-data, not JSON.

ParameterInTypeDescription
image *multipartfileMax 5MB

Request

curl -X POST https://api.temsreco.com/api/products/PRODUCT_ID/images \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -F "image=@panel-photo.jpg"

Response

{ "success": true, "message": "Image uploaded", "data": { "product": { "images": [ { "url": "https://res.cloudinary.com/...", "publicId": "tems-products/..." } ] } } }
DELETE/products/{id}/imagesRequires partner account

Remove an image from a product.

ParameterInTypeDescription
publicId *bodystringFrom the image object returned by the upload endpoint

Response

{ "success": true, "message": "Image removed", "data": { "product": { "...": "..." } } }

Bulk import

Upload a CSV/XLSX of products instead of creating them one at a time — useful for onboarding a large catalogue. This is a two-step flow: parse (preview, nothing is saved) then commit (creates only the rows you confirm).

GET/products/import/templateRequires partner account

Download a CSV template with the expected columns and one example row.

Response

Content-Type: text/csv — a downloadable file, not a JSON envelope.
POST/products/import/parseRequires partner account

Upload a CSV/XLSX (multipart/form-data) and get back a row-by-row validity preview. Nothing is saved at this step. Any column not in the known set (name, category, manufacturer, description, retailPrice, bnplEligible, warranty, certifications, sku, stockStatus, isPublished) is automatically folded into that row's free-form specs.

ParameterInTypeDescription
file *multipartfileCSV or XLSX, max 5MB, max 500 rows

Response

{
  "success": true,
  "data": {
    "rows": [
      { "rowNumber": 2, "valid": true, "errors": [], "data": { "name": "...", "...": "..." } },
      { "rowNumber": 3, "valid": false, "errors": ["name is required"], "data": { "...": "..." } }
    ],
    "summary": { "total": 2, "valid": 1, "invalid": 1 }
  }
}
POST/products/import/commitRequires partner account

Create products from previously parsed rows. Send back only the rows you want created (typically the valid ones from the parse step). New products from bulk import default to unpublished — review and publish them individually once created.

ParameterInTypeDescription
rows *bodyobject[]Each row's data object, as returned by the parse step

Response

{ "success": true, "message": "Imported 12 product(s)", "data": { "createdCount": 12, "failed": [] } }

Questions about integrating? Email support@tecadenergies.com.ng. See also our Terms of Service and Privacy Policy.