Memra PHP SDK — Quickstart

Get Memra running in a Laravel app in about 10 minutes. This guide assumes you already have a Memra API key (sign up at usememra.com).

Not on Laravel? Jump to the Plain PHP block at the bottom. Everything else in this guide applies equally.


1. Install

composer require memra/sdk

Requires PHP 8.1+ and Guzzle 7.5+ (already shipped with Laravel). The service provider and Memra facade auto-register via package discovery — no manual wiring needed.

2. Configure auth (one recommended pattern)

Add one line to your .env:

MEMRA_API_KEY=memra_live_your_key_here

That's it. The service provider builds a singleton client for you, reads the key from config, and binds the Memra facade. Reuse the facade anywhere — controller, job, command, whatever.

Optional: publish the config (php artisan vendor:publish --tag=memra-config) if you need to change base_url or timeout.


3. Hello memory

Store a fact, recall it by meaning, and see how much context it will cost you.

use Memra\Sdk\Facades\Memra;

// Store a memory
$memory = Memra::add(
    content: 'Alice prefers dark mode and drinks her coffee black.',
    tenantId: 'user_alice',
    projectId: 'my-app',
    type: 'preference',
    importance: 7,
);

logger()->info("stored {$memory->id}");

// Recall by meaning (reranking is on by default)
$result = Memra::recall(
    query: 'How does Alice like her coffee?',
    tenantId: 'user_alice',
    projectId: 'my-app',
    rerank: true,
);

foreach ($result->data as $mem) {
    echo sprintf("[%.3f] %s\n", $mem->score, $mem->content);
}

echo "\nestimated_tokens for this recall: {$result->estimatedTokens}\n";

What to notice:

  • tenantId scopes the memory to one user. Always pass it.
  • rerank: true is the default; passing it explicitly is just a reminder.
  • $result->estimatedTokens is the total tokens you're about to inject into your LLM prompt if you pass all $result->data into it. Read it every call — your context budget depends on it.
  • The PHP client retries 429 / 5xx / network errors 3× with jittered backoff out of the box. You don't have to add your own wrapper for transient failures.

4. A realistic example: episodic memory for a chatbot

Store a few conversation turns, then recall the ones relevant to a new question. This is where reranking earns its keep — the top dense-similarity hit is often not the answer you want.

use Memra\Sdk\Facades\Memra;

$tenant  = 'user_alice';
$project = 'support-bot';

$turns = [
    'Alice reported her export job failed on 2026-04-10 at 14:02 UTC.',
    'Root cause: she exceeded the 10k-row free-tier limit.',
    'Alice upgraded to the Pro plan the next day and the export succeeded.',
    'Alice asked whether older exports were preserved — yes, retained 90 days.',
];

foreach ($turns as $turn) {
    Memra::add(
        content:   $turn,
        tenantId:  $tenant,
        projectId: $project,
        type:      'event',
    );
}

// Later: Alice comes back and asks something fuzzy.
$result = Memra::recall(
    query:     'Why did my last export break and did I fix it?',
    tenantId:  $tenant,
    projectId: $project,
    limit:     5,
);

echo "top hit: {$result->data[0]->content}\n";
echo "returned {$result->meta['returned']} of {$result->meta['total_candidates']} candidates\n";
echo "tokens to inject: {$result->estimatedTokens}\n";

Why reranking matters here: pure vector similarity often surfaces the "older exports preserved?" turn because it has the richest lexical overlap with "export". The reranker reads the actual question — why did it break and did I fix it — and promotes the root-cause + resolution turns to the top.

For bulk ingestion, swap the loop for Memra::batch([...]) — up to 100 items in one round-trip.


5. Testing without hitting the API

Use Memra::fake() in Pest or PHPUnit. No HTTP, no real memories, just assertions.

use Memra\Sdk\Facades\Memra;

it('stores a user preference on signup', function () {
    $fake = Memra::fake();

    // Your app code under test
    (new RegisterUser)->handle(['id' => 'user_alice', 'theme' => 'dark']);

    $fake->assertAdded(fn (array $args) =>
        $args['tenantId'] === 'user_alice'
        && $args['type'] === 'preference'
    );
});

Useful assertions: assertAdded(), assertRecalled(), assertDeleted(), assertNothingSent() — each takes an optional closure for fine-grained matching.


6. Handling errors

Catch the typed subclass you care about; let the rest bubble up. MemraValidationException::$errors gives you per-field messages; MemraQuotaException::$retryAfter is populated from the Retry-After header.

use Memra\Sdk\Exceptions\MemraAuthException;
use Memra\Sdk\Exceptions\MemraQuotaException;
use Memra\Sdk\Exceptions\MemraValidationException;

try {
    $result = Memra::recall(query: '...', tenantId: '...', projectId: '...');
} catch (MemraAuthException) {
    abort(500, 'Check MEMRA_API_KEY — it is missing or revoked.');
} catch (MemraQuotaException $e) {
    // Retry middleware already tried 3×. If we're here, give up cleanly.
    abort(503, "Rate limited. Try again in {$e->retryAfter}s.");
} catch (MemraValidationException $e) {
    // Show the first field error
    $first = array_key_first($e->errors);
    abort(422, "{$first}: {$e->errors[$first][0]}");
}

Plain PHP (one block)

If you aren't on Laravel, skip the facade and construct the client directly:

use Memra\Sdk\MemraClient;

$client = new MemraClient(apiKey: getenv('MEMRA_API_KEY') ?: throw new RuntimeException('MEMRA_API_KEY missing'));

$memory = $client->memories()->add(
    content:   'Alice prefers dark mode.',
    tenantId:  'user_alice',
    projectId: 'my-app',
);

$result = $client->memories()->recall(
    query:     'What does Alice prefer?',
    tenantId:  'user_alice',
    projectId: 'my-app',
);

echo $result->data[0]->content, "\n";
echo "tokens: {$result->estimatedTokens}\n";

Next steps

  • Multiple users? One tenantId per user. Use $client->projects()->create(...) once to carve apps apart.
  • Correcting an existing memory? Call Memra::supersede($id, content: '...') instead of adding a new one — the old memory stops surfacing in search and the audit trail is preserved. Memra::chain($id) returns the full oldest→newest history.
  • Compliance? $client->export()->account() and $client->erasure()->create($tenant).
  • Background jobs? The facade is safe in Laravel queue workers; the singleton is resolved per-worker.

Full reference: usememra.com/docs/sdks/php.