Skip to content

Make your first request ​

This request checks your endpoint, API key, and HMAC signing without creating a customer or moving funds. You need development credentials from Agio and a Node.js or Bun runtime that can run TypeScript.

Set your credentials ​

Set AGIO_PARTNER_API_TOKEN and AGIO_PARTNER_CLIENT_SECRET in your server environment. Keep them out of browser code and source control. This example defaults to development; set AGIO_PARTNER_ENDPOINT to the production URL when you are ready to use production credentials.

bash
export AGIO_PARTNER_API_TOKEN='<development API token>'
export AGIO_PARTNER_CLIENT_SECRET='<development signing secret>'

Send a signed query ​

Save this as partner-request.ts, partner_request.py or partner_request.go. Serialize the JSON body once, sign that string, and send the same string.

typescript
import { createHmac } from "node:crypto";

function requiredEnv(name: string) {
  const value = process.env[name];
  if (!value) throw new Error(`Missing ${name}`);
  return value;
}

const endpoint = process.env.AGIO_PARTNER_ENDPOINT ?? "https://dev.api.agiodigital.com/partner/cards/graphql";
const apiToken = requiredEnv("AGIO_PARTNER_API_TOKEN");
const clientSecret = requiredEnv("AGIO_PARTNER_CLIENT_SECRET");

export async function partnerQuery(query: string, variables: Record<string, unknown> = {}) {
  const body = JSON.stringify({ query, variables });
  const timestamp = Date.now().toString();
  const signature = createHmac("sha256", clientSecret).update(`${timestamp}.${body}`).digest("hex");

  const response = await fetch(endpoint, {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-agio-api-key": apiToken,
      "x-agio-timestamp": timestamp,
      "x-agio-signature": signature
    },
    body
  });

  if (!response.ok) throw new Error(`Agio returned HTTP ${response.status}`);
  const result = await response.json();
  if (result.errors?.length) throw new Error(result.errors.map((error: { message: string }) => error.message).join("; "));
  return result.data;
}

const data = await partnerQuery("query CheckConnection { __typename }");
if (data.__typename !== "Query") throw new Error("Unexpected response");
console.log("Partner API connection verified");
python
import hashlib
import hmac
import json
import os
import time

import httpx

ENDPOINT = os.environ.get("AGIO_PARTNER_ENDPOINT", "https://dev.api.agiodigital.com/partner/cards/graphql")
API_TOKEN = os.environ["AGIO_PARTNER_API_TOKEN"]
CLIENT_SECRET = os.environ["AGIO_PARTNER_CLIENT_SECRET"].encode()


def partner_query(query: str, variables: dict | None = None) -> dict:
    body = json.dumps({"query": query, "variables": variables or {}}).encode()
    timestamp = str(int(time.time() * 1000))
    signature = hmac.new(CLIENT_SECRET, timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
    response = httpx.post(
        ENDPOINT,
        content=body,
        timeout=30,
        headers={
            "content-type": "application/json",
            "x-agio-api-key": API_TOKEN,
            "x-agio-timestamp": timestamp,
            "x-agio-signature": signature,
        },
    )
    if not response.is_success:
        raise RuntimeError(f"Agio returned HTTP {response.status_code}")
    result = response.json()
    if result.get("errors"):
        raise RuntimeError("; ".join(error["message"] for error in result["errors"]))
    return result["data"]


if __name__ == "__main__":
    data = partner_query("query CheckConnection { __typename }")
    if data.get("__typename") != "Query":
        raise RuntimeError("Unexpected response")
    print("Partner API connection verified")
go
package main

import (
	"bytes"
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

var httpClient = &http.Client{Timeout: 30 * time.Second}

func requiredEnv(name string) (string, error) {
	value := os.Getenv(name)
	if value == "" {
		return "", fmt.Errorf("missing %s", name)
	}
	return value, nil
}

func PartnerQuery(ctx context.Context, query string, variables map[string]any, out any) error {
	endpoint := os.Getenv("AGIO_PARTNER_ENDPOINT")
	if endpoint == "" {
		endpoint = "https://dev.api.agiodigital.com/partner/cards/graphql"
	}
	apiToken, err := requiredEnv("AGIO_PARTNER_API_TOKEN")
	if err != nil {
		return err
	}
	clientSecret, err := requiredEnv("AGIO_PARTNER_CLIENT_SECRET")
	if err != nil {
		return err
	}
	if variables == nil {
		variables = map[string]any{}
	}
	body, err := json.Marshal(map[string]any{"query": query, "variables": variables})
	if err != nil {
		return err
	}
	timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
	mac := hmac.New(sha256.New, []byte(clientSecret))
	mac.Write([]byte(timestamp + "."))
	mac.Write(body)

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
	if err != nil {
		return err
	}
	req.Header.Set("content-type", "application/json")
	req.Header.Set("x-agio-api-key", apiToken)
	req.Header.Set("x-agio-timestamp", timestamp)
	req.Header.Set("x-agio-signature", hex.EncodeToString(mac.Sum(nil)))

	resp, err := httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		return fmt.Errorf("Agio returned HTTP %d", resp.StatusCode)
	}
	var result struct {
		Data   json.RawMessage `json:"data"`
		Errors []struct {
			Message string `json:"message"`
		} `json:"errors"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return err
	}
	if len(result.Errors) > 0 {
		messages := make([]string, len(result.Errors))
		for i, e := range result.Errors {
			messages[i] = e.Message
		}
		return errors.New(strings.Join(messages, "; "))
	}
	return json.Unmarshal(result.Data, out)
}

func main() {
	var data struct {
		Typename string `json:"__typename"`
	}
	if err := PartnerQuery(context.Background(), "query CheckConnection { __typename }", nil, &data); err != nil {
		log.Fatal(err)
	}
	if data.Typename != "Query" {
		log.Fatal("Unexpected response")
	}
	fmt.Println("Partner API connection verified")
}

Run it with bun partner-request.ts, python partner_request.py (after pip install httpx) or go run partner_request.go. In your own Go service, move PartnerQuery into your package; later Go examples call it from package partner. The output should be Partner API connection verified. Unlike a __schema introspection query, __typename requires a valid HMAC signature, so this verifies the complete request path.

A 401 means the key, signature, or timestamp was rejected. Check the environment, use milliseconds for the timestamp, and compare the signed body with the sent body. See authentication for the exact contract.

The other examples in this guide provide a GraphQL operation and JSON variables. Send them with the same partnerQuery function. A GraphQL response without errors can still contain success: false; check each operation's result before using its resource identifiers.

Get to your first funded card ​

  1. Provision a customer organization and cardholder. Store the returned organization and cardholder identifiers.
  2. Collect agreement acceptance. Show the cardholder the agreements for your program, hash each document, and record the current agreementVersion and acceptedAt. Every application needs this evidence, or it returns ATTESTATION_REQUIRED.
  3. Verify the cardholder and submit the application with agreementsAcceptance. Choose createWallet: true for treasury-managed collateral, or supply the customer's own collateral-admin address.
  4. Wait for approval, then create a virtual card with the numeric application ID.
  5. Read the deposit address and fund the collateral. Use the chain and accepted token for that application.
  6. Check spending power and subscribe to webhooks for application and transaction updates.

The Bun starter kit includes a provisioning script and an Express webhook receiver. Use the signing and lifecycle contracts in these pages when adapting it to your application.